TT Lab
블로그 시작하기
배우기 러닝패스 코스

서버를 처음부터 잠근다

코드가 뚫려도 프로세스를 가둔다

TT Lab 에서 이어서 보기

이 실습은 VM 에서 돕니다

우분투 24.04 VM 한 대입니다. root 이고 systemd 와 커널 기능이 그대로 있습니다. 실습 파드에는 capability 가 없어 이 실습이 다루는 설정을 바꿀 수 없기 때문에 VM 으로 돕니다. 처음 뜨는 데 몇 분이 걸립니다. 세션이 끝나면 VM 은 통째로 사라지므로 남기고 싶은 것은 화면에서 복사해 두세요.

목표

경로 우회 취약점이 있는 파일 서비스를 AppArmor 프로필로 가둡니다. 코드를 고치지 않아도, 설령 코드가 뚫려도 그 프로세스가 /srv/files 밖을 읽지 못하게 만듭니다. 고친 뒤 우회가 막히는지, 정상 파일은 여전히 열리는지, 위반이 로그에 남는지를 확인합니다.

왜 중요한가

방어는 한 겹이 아닙니다. 애플리케이션 코드의 입력 검증은 앱 팀이 고칠 일이지만, 그 수정이 배포되기 전이나 또 다른 우회가 발견됐을 때를 대비해 운영은 그 프로세스가 할 수 있는 일 자체를 좁혀 둡니다. AppArmor 는 프로세스가 읽고 쓸 수 있는 경로와 쓸 수 있는 기능을 프로필로 못박습니다. 허용 목록에 없는 것은 root 권한이 있어도 커널이 막습니다.

함정이 둘 있습니다. 하나는 complain 과 enforce입니다. complain 모드는 위반을 기록만 하고 막지 않습니다. 켰다고 안심하기 쉽지만 실제로는 아무것도 막지 않습니다. 다른 하나는 프로필을 올리는 것과 서비스에 붙이는 것이 다른 일이라는 점입니다. 커널에 프로필이 있어도 systemd 가 서비스를 그 프로필로 띄우지 않으면 프로세스는 unconfined 입니다.

단계

  1. 경로 우회로 /etc/shadow 가 읽히는 것을 /root/aa/01-leak.txt 에 기록합니다.
  2. /etc/apparmor.d/labhub-filesvc 에 프로필을 씁니다.
  3. enforce 모드로 올립니다.
  4. systemd 로 서비스에 붙이고 다시 띄웁니다.
  5. 우회가 막히고 로그에 남는지 /root/aa/05-contained.txt 에 확인합니다.
  6. 정상 파일이 되는지 /root/aa/06-ok.txt 에 확인합니다.
  7. 부팅을 넘기는지 확인합니다.

참고

경로 우회를 관찰한다

서비스가 ?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 로 확인합니다.