코드가 뚫려도 프로세스를 가둔다
이 실습은 VM 에서 돕니다
우분투 24.04 VM 한 대입니다. root 이고 systemd 와 커널 기능이 그대로 있습니다. 실습 파드에는 capability 가 없어 이 실습이 다루는 설정을 바꿀 수 없기 때문에 VM 으로 돕니다. 처음 뜨는 데 몇 분이 걸립니다. 세션이 끝나면 VM 은 통째로 사라지므로 남기고 싶은 것은 화면에서 복사해 두세요.
목표
경로 우회 취약점이 있는 파일 서비스를 AppArmor 프로필로 가둡니다. 코드를 고치지 않아도, 설령 코드가 뚫려도 그 프로세스가 /srv/files 밖을 읽지 못하게 만듭니다. 고친 뒤 우회가 막히는지, 정상 파일은 여전히 열리는지, 위반이 로그에 남는지를 확인합니다.
왜 중요한가
방어는 한 겹이 아닙니다. 애플리케이션 코드의 입력 검증은 앱 팀이 고칠 일이지만, 그 수정이 배포되기 전이나 또 다른 우회가 발견됐을 때를 대비해 운영은 그 프로세스가 할 수 있는 일 자체를 좁혀 둡니다. AppArmor 는 프로세스가 읽고 쓸 수 있는 경로와 쓸 수 있는 기능을 프로필로 못박습니다. 허용 목록에 없는 것은 root 권한이 있어도 커널이 막습니다.
함정이 둘 있습니다. 하나는 complain 과 enforce입니다. complain 모드는 위반을 기록만 하고 막지 않습니다. 켰다고 안심하기 쉽지만 실제로는 아무것도 막지 않습니다. 다른 하나는 프로필을 올리는 것과 서비스에 붙이는 것이 다른 일이라는 점입니다. 커널에 프로필이 있어도 systemd 가 서비스를 그 프로필로 띄우지 않으면 프로세스는 unconfined 입니다.
단계
- 경로 우회로
/etc/shadow가 읽히는 것을/root/aa/01-leak.txt에 기록합니다. /etc/apparmor.d/labhub-filesvc에 프로필을 씁니다.- enforce 모드로 올립니다.
- systemd 로 서비스에 붙이고 다시 띄웁니다.
- 우회가 막히고 로그에 남는지
/root/aa/05-contained.txt에 확인합니다. - 정상 파일이 되는지
/root/aa/06-ok.txt에 확인합니다. - 부팅을 넘기는지 확인합니다.
참고
- 프로필 올리기:
apparmor_parser -r /etc/apparmor.d/labhub-filesvc, 상태:aa-status, 프로세스 상태:cat /proc/<pid>/attr/current. - 거부 로그:
journalctl -k | grep 'apparmor="DENIED"'. - 흔한 실수: 프로필을 complain 으로 올리는 것. enforce 여야 막습니다.
- 흔한 실수: 프로필만 올리고
AppArmorProfile=로 붙이지 않는 것. 프로세스는 unconfined 로 남습니다.
경로 우회를 관찰한다
서비스가 ?name= 의 .. 를 막지 않아 /etc/shadow 가 읽히는 것을 확인해 /root/aa/01-leak.txt 에 저장하세요. 정상 파일(readme.txt)과 우회(../../etc/shadow)를 둘 다 요청해 응답 앞부분을 적습니다.
URL 에서 / 는 %2F 로 적거나 그대로 ../ 를 씁니다. curl -s 'http://127.0.0.1:8081/?name=../../etc/shadow' 로 무엇이 돌아오는지 보세요. 이 단계는 고치기 전의 피해를 기록하는 것입니다.
프로필을 쓴다
/etc/apparmor.d/labhub-filesvc 에 AppArmor 프로필 labhub-filesvc 를 쓰세요. python3 실행, 기본·python 추상화, /opt/filesvc/ 읽기, /srv/files/ 읽기, inet stream 소켓을 허용하고, 그 밖의 파일 읽기는 허용하지 않습니다. 아직 서비스에 붙이지는 않습니다.
프로필 뼈대는 profile labhub-filesvc flags=(attach_disconnected) { ... } 입니다. include <abstractions/base> 와 <abstractions/python> 을 넣고, 허용은 /srv/files/** r, 처럼 적습니다. 허용 목록에 없는 읽기는 enforce 모드에서 자동으로 막힙니다 — /etc/shadow 를 명시적으로 거부할 필요는 없습니다.
프로필을 enforce 로 올린다
프로필을 커널에 enforce 모드로 올리세요. aa-status 에 labhub-filesvc 가 enforce 로 보여야 합니다.
apparmor_parser -r <파일> 이 프로필을 올립니다(다시 올릴 때도 -r). complain 이 아니라 enforce 로 올라갔는지는 aa-status 로 확인합니다. complain 으로 올렸다면 aa-enforce 로 바꿀 수 있습니다.
서비스에 붙이고 다시 띄운다
systemd 드롭인으로 filesvc 에 AppArmorProfile=labhub-filesvc 를 붙이고 다시 띄우세요. 돌고 있는 프로세스의 /proc/<pid>/attr/current 가 labhub-filesvc (enforce) 여야 합니다.
systemctl edit filesvc 또는 /etc/systemd/system/filesvc.service.d/aa.conf 에 [Service] 와 AppArmorProfile=labhub-filesvc 를 둡니다. daemon-reload 와 restart 를 잊지 마세요. 메인 PID 는 systemctl show -p MainPID 로 봅니다.
우회가 막히는가
이제 경로 우회로 /etc/shadow 를 읽으려 하면 막히는지 확인하고, 그 시도가 apparmor="DENIED" 로 커널 로그에 남는지 확인해 /root/aa/05-contained.txt 에 응답과 로그 한 줄을 저장하세요.
같은 우회 요청을 다시 보냅니다. 이제 서비스는 파일을 못 읽어 오류를 돌려줘야 합니다. 커널 로그의 거부는 journalctl -k --since -2min | grep 'apparmor="DENIED"' 로 찾습니다.
정상 파일은 되는가
가둔 뒤에도 정상 파일은 그대로 열리는지 확인해 /root/aa/06-ok.txt 에 저장하세요. readme.txt 와 price.txt 가 200 과 원래 내용을 돌려줘야 합니다.
프로필이 너무 좁으면 서비스가 자기 파일도 못 읽습니다. /srv/files/ 아래 읽기를 허용했는지 다시 보세요. 방어가 정상 업무를 막으면 운영이 그 프로필을 꺼 버립니다.
부팅을 넘긴다
프로필이 부팅 때 자동으로 올라오는지 확인하세요. apparmor 서비스가 활성이어야 하고, 프로필 파일이 /etc/apparmor.d/ 아래에 있어야 하며, 프로필을 커널에서 내렸다 systemctl reload apparmor 로 되살렸을 때 다시 enforce 로 올라와야 합니다.
/etc/apparmor.d/ 아래의 프로필은 apparmor.service 가 부팅 때 전부 올립니다. 되살아나는지는 apparmor_parser -R <파일> 로 내린 뒤 systemctl reload apparmor 로 확인합니다.