본문 바로가기

개발/PHP

운영중 PHP 사이트 SQL 인젝션 당한 후기

2011년에 개발된 한국리더십학교 사이트를 운영하게 됐다.

SQL 인젝션 공격을 받았고, 급하게 처리할 수 있고 빠르게 연락이 되는 인원이 나 뿐이어서
흥미로운 마음으로 이 사건을 접근하게 됐다.

 

✔ 결론부터 말하자면

익명 사용자가 남긴 댓글들은 전형적인 SQL 인젝션 자동 스캐닝 공격이다.
MS-SQL, MySQL을 동시에 겨냥한 에러 기반 페이로드들이 섞여 있었고, 목적은 우리 서비스(PHP 기반 한국리더십학교 사이트)에 DB 메타정보를 뽑아내는지 확인하는 탐지 공격이었다.


1. 서론 — 어떻게 발견했나

어느 날 사이트에 뜬금없이 댓글이 달렸다. “555”, “convert(int,CHAR(...))”, “(select ... information_schema ...)” 같은 이상한 문자열들. 처음엔 장난인가 싶었는데, 로그를 뜯어보니 패턴이 너무 노골적이었다.
그래서 직접 전부 디코딩하고 쿼리 동작까지 확인했다. 결론은 단순 탐색기가 아니라 SQL 인젝션 취약점을 자동으로 찾는 스캐너의 흔적이었다.


2. 본론 — 공격자가 실제로 시도한 것들

2-1. CHAR(...) 조합으로 숨겨둔 문자열 삽입

공격자가 넣은 CHAR(52)+CHAR(67)+CHAR(117)... 이런 형태는
디코딩하면 4CuaGAPEypw, 4CuVG33izsz 같은 고유 마커 문자열이다.
이건 스캐너가 에러 메시지나 응답 HTML에 이 문자열이 섞여 나오는지 확인하기 위해 사용한다.

  • MS-SQL → convert(int, CHAR(...)) 로 일부러 타입 에러를 발생
  • MySQL → concat(CHAR(...), floor(rand()*2)) + group by 로 duplicate key 에러 유도
    이렇게 하면 에러 메시지 안에 저 문자열이 섞여 나온다.

즉, “내가 넣은 문자열이 서버에서 튀어나오면 인젝션 성공” 을 자동으로 판단하는 방식.

2-2. DB 종류를 가리지 않고 다 때려본 멀티 계열 공격

로그를 보면 재미있게도 공격자가 DBMS를 특정하지 않았다.
MS-SQL, MySQL 페이로드가 섞여 있다.

  • syscolumns, convert(int, ...) → MS-SQL 전용
  • information_schema.tables, rand(), limit → MySQL 전용

자동화된 스캐너(예: sqlmap 계열 커스텀 플러그인)에서 자주 보이는 패턴이다.
즉, “지금 서버가 무슨 DB인지 모르겠으니 일단 다 쏴본다”는 식의 광범위 스캔.

2-3. 공격 목적: 내부 테이블, 컬럼 이름 유출

특히 아래 페이로드가 의도를 가장 잘 드러낸다:

(select concat(
    (select concat(CHAR(...)) from information_schema.tables limit 0,1),
    floor(rand()*2)
))

이건 딱 테이블 이름 유출(error-based) 을 노린 클래스틱 패턴이다.
information_schema.tables 에서 테이블 이름 하나를 가져오고,
랜덤 값과 섞어서 duplicate entry 에러를 만들고,
그 에러 메시지 안에서 테이블 이름을 회수한다.

즉, 초기 정찰 단계의 공격.


3. 결론 — 최종 판단과 조치

이건 분명한 SQL 인젝션 탐지 공격이다.
데이터를 훔치려는 단계는 아니지만, 취약점이 있으면 즉시 후속 공격이 들어오게 된다.

나는 다음처럼 바로 대응했다:

  1. 해당 IP 차단
  2. 웹/앱 로그에서 CHAR() 패턴 전수 검색해 추가 시도 존재 여부 확인
  3. PHP 코드 전수 점검 후 Prepared Statement 로 확실히 고정
  4. 에러 메시지 공개 비활성화(스택 노출 제거)
  5. WAF 룰에 CHAR(, information_schema, syscolumns 패턴 추가

구체적인 실행은 아래 정리했다.

더보기

✔ 전체 흐름

공격자 패턴이 로그에 남아있는지 먼저 확인

공격 IP 차단

웹서버 에러 메시지 노출 제거

PHP 코드에서 Prepared Statement 강제 적용 확인

WAF / ModSecurity 룰 추가

서버 전체 업데이트 및 재기동

1. 서버 접속
ssh root@<서버_IP>

2. 웹/시스템 로그에서 공격 흔적 찾기

SQL Injection 스캐너가 남기는 흔한 패턴을 grep으로 검색한다.

2-1. nginx 로그 확인 (nginx 사용 시)
grep -RniE "CHAR\(|information_schema|syscolumns|rand\(\)" /var/log/nginx/

2-2. Apache 로그 확인 (Apache 사용 시)
grep -RniE "CHAR\(|information_schema|syscolumns|rand\(\)" /var/log/httpd/

2-3. PHP 로그 확인
grep -RniE "CHAR\(|information_schema|syscolumns" /var/log/php*

목적

공격자가 몇 번이나 시도했는지

같은 IP에서 들어오는지

URL 패턴이 무엇인지 확인

3. 공격자 IP 차단

로그에서 찾은 IP가 <ATTACKER_IP> 라고 하면:

3-1. UFW 사용 시
ufw deny from <ATTACKER_IP>
ufw reload

3-2. iptables 사용 시
iptables -A INPUT -s <ATTACKER_IP> -j DROP
service iptables save

3-3. firewalld 사용 시
firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='<ATTACKER_IP>' reject"
firewall-cmd --reload

4. 에러 메시지 외부 노출 차단

SQL 인젝션은 에러 기반(error-based) 공격이기 때문에
PHP와 웹서버에서 에러가 브라우저로 그대로 노출되면 매우 위험하다.

4-1. PHP 설정
vi /etc/php.ini


아래 값을 반드시 이렇게 설정:

display_errors = Off
log_errors = On
error_log = /var/log/php-fpm/error.log

PHP-FPM 재시작
systemctl restart php-fpm

5. PHP 코드 보안 조치 (Prepared Statement 강제 적용)
5-1. 서비스 코드 디렉토리 이동
cd /var/www/<your-project>

5-2. 취약한 패턴을 전수 검색
grep -RniE "\$_GET|\$_POST|mysql_query|mysqli_query|PDO->query" .

5-3. 특히 $_GET + 직접 SQL 연결 패턴 찾기
grep -RniE "SELECT|INSERT|UPDATE|DELETE" . | grep "\$_"

5-4. 발견되면 PDO prepare 로 변환

예시 (명령어는 아님, 참고용 코드):

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);

6. WAF 룰 설정 (ModSecurity)

사이트가 외부 공격을 자주 받는다면 웹서버에 WAF 설치를 권장.

6-1. ModSecurity 설치 (nginx 기준)
yum install mod_security -y


(apt 계열)

apt install libapache2-mod-security2 -y

6-2. 커스텀 SQL Injection 차단 룰 추가

규칙 파일 열기:

vi /etc/modsecurity/modsecurity.conf


아래 추가:

SecRule ARGS "(?i:CHAR\(|information_schema|syscolumns|concat\(.*rand\()" "id:12345,phase:2,deny,status:403,msg:'SQLi pattern blocked'"


WAF 재시작:

systemctl restart nginx
# 또는
systemctl restart httpd

7. fail2ban 으로 자동 차단 활성화

SQL Injection 시도 같은 악성 패턴이 일정 횟수 나오면 자동 차단.

yum install fail2ban -y


설정 파일 복사:

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local


SQLi 패턴용 커스텀 jail 추가:

vi /etc/fail2ban/jail.local

[sqli-attack]
enabled = true
port = http,https
filter = sqli-attack
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 86400
findtime = 600


필터 생성:

vi /etc/fail2ban/filter.d/sqli-attack.conf

[Definition]
failregex = .*CHAR\(.*|.*information_schema.*|.*syscolumns.*


재시작:

systemctl restart fail2ban

8. 전체 업데이트

서버 전체 취약점 업데이트:

yum update -y
# 또는
apt update && apt upgrade -y

9. 웹서버 재기동
systemctl restart nginx
# 또는
systemctl restart httpd

📌 요약: “관리자가 실제 해야 할 실질 조치”

로그 검색 → 공격 패턴 파악

IP 차단

에러 메시지 노출 Off

PHP 코드에서 Prepared Statement 미적용 부분 전수 점검

ModSecurity / fail2ban 적용해서 재발 방지

시스템 업데이트 및 재시작

이번 사례는 “PHP 기반 서비스에서 인풋 검증을 조금이라도 허술하게 하면 어떤 공격을 맞게 되는가”를 그대로 보여주는 전형적인 예였다.
덕분에 서비스 전체 보안 레벨을 한 단계 더 끌어올릴 계기가 됐다.

반응형