SQLite 파일 하나가 실행 파일, 웹 서버, 방문 로그가 되는 SELF 실험
- SELF는 프로그램을 SQLite 데이터베이스로 저장하고, Linux binfmt_misc가 호출한 전용 인터프리터가
segments테이블 행을 메모리에 매핑한 뒤 엔트리 포인트로 점프하는 실행 형식임 - PoC 웹 서버 self-httpd는 프로그램 코드, 웹 페이지, 라우트, 방문 기록과 버튼 입력을 한 SQLite 파일에 담고, 실행 중 같은 파일의
presses와visits테이블을 트랜잭션으로 갱신함 - binfmt_misc는 원본 파일 대신 인터프리터를 exec하므로 현재는
/proc/self/exe를 쓸 수 없지만, self-exec가argv[0]에 원본 실행 파일 경로를 넘기고 SQLite 연결을 닫은 뒤 제어를 넘겨 프로그램이 자기 파일을 다시 열게 함 - 작성자는 ZIP 컨테이너와 Lua 훅을 쓰는 redbean과 비교해, SELF에서는
handlers테이블에 SQL 행을 추가하는 방식으로 라우트를 정의한다며 이를 'Actually Queryable Executable'이라 부름
Hacker News opinions
SQL 인젝션에서 ACE로 이어지는 경로가 생긴다는 발상 자체가 엄청나다.
인터프리터가 마운트 네임스페이스를 만들고 SQLite가 뒷받침하는 가상 파일시스템을 마운트하면 어떨까 싶다. 그러면 기존 프로그램도 소스를 고치지 않고 파일 접근까지 한 파일에 가둘 수 있음.
이런 비트 배치 장난은 늘 사랑스럽다. PoC||GTFO 14호에는 자기 자신의 MD5를 NES로 렌더링하는 PDF 겸 NES ROM도 있었음.
배포 전에 바이너리에 스키마를 덧붙이는 대신, 시작할 때마다 마이그레이션을 돌리면 된다. 기존 파일에 새 SELF 데이터와 세그먼트를 넣고 SIGHUP 뒤 fork+exec와 FD handover를 하면 무중단 업그레이드도 설계할 수 있을 듯.
인터넷에 노출된 서비스 바이너리가 자기 자신을 쓸 수 있다면 SQL 인젝션의 파급력이 완전히 달라짐.
OS/400의 라이브러리가 생각난다. AS/400에서는 라이브러리가 DB 테이블 비슷하면서도 파일처럼 일급 OS 객체이고, 명령줄에서 SQL도 바로 쓸 수 있음.
정적 콘텐츠를 바이너리에 넣는 건 납득하지만, 실행 중 데이터를 거기에 쓰는 건 지저분하게 느껴진다. 난 읽기 전용 바이너리와 별도 쓰기 가능 상태 디렉터리가 더 좋음. 그래도 이런 해커 감성 프로젝트는 끝까지 밀어붙였으면 한다.
같은 바이너리 인스턴스를 여러 개 돌리면 상태는 어떻게 처리하지?
SQLite를 컨테이너로 쓰는 건 귀엽지만 Lua 바이트코드에도 이 방식을 쓰고 싶다. 앱 상태와 런타임을 바이너리 하나에 담아 다른 머신으로 옮긴 뒤 복구하는 마이그레이션이 가능해질 듯.
애초에 애플리케이션이 자기 자신을 바꾸도록 속이기 쉬워져서 보안 문제가 커 보인다. 권한 있는 경로에 놓이면 상태도 못 쓰고, 다중 사용자 앱이면 사용자마다 복사본이 필요하다. 실행 코드 테이블과 런타임 테이블을 분리해 쓰기를 막을 ACL 비슷한 장치가 필요함.
SQLite VFS가 특정 페이지를 OS 메모리 페이지에 매핑하고 mprotect()로 막으면 된다. 실행 페이지를 고치려 하면 W^X 때문에 죽게 만들 수 있음. 다만 SQLite의 mmap과 write() 혼용, mseal()까지 엮어야 하고 보안이 중요한 곳에서는 이런 해킹을 안 쓰는 편이 낫다.
웹 서버가 웹에서 코드 세그먼트를 받아서 플러그인처럼 자기 파일에 추가할 수도 있나?
Nginx가 요청을 DB로 넘기고, DB 안의 코드를 실행해 응답한 뒤 프로세스를 종료하는 서버리스 비슷한 구조도 만들 수 있겠다. crontab으로 DB의 작업을 깨우고, 권한 있는 사용자가 같은 DB에서 코드를 바꿔 실행하게 할 수도 있음.
OS/400 얘기가 나왔으니 MUMPS의 globals, 특히 ^rOBJ도 떠오른다. TandemOS도 비슷한 계열임.