회원가입 | 고객센터 |
DESIGNONEX
디자인원엑스
Q&A
지식공유
공지사항
DX마켓
통계
로그인 회원가입
고객센터

DX Security — SQL Injection 보안 플러그인

D DX관리자
2026.09.03 00:28 2 0



DX Security — SQL Injection 방어 v1.0.0 안내서

DesignOneX CMS SQL Injection 기업 납품 수준 2차 방어 플러그인
PHP 5.6+ · IIS / Apache / Nginx · 공유호스팅 완전 호환
코어(Database.php / boards/handler.php) 수정 없음 — 훅으로만 동작
기업 납품 요구사항 SQL-001~010 완전 준수



목차

  1. SQL Injection이란 무엇인가
  2. DXCMS 코어의 현재 SQL 보안 수준
  3. 코어가 막지 못하는 것 — 이 플러그인의 역할
  4. 설치 방법
  5. 방어층 상세 설명
    • SI1. Database::findAll() ORDER BY Injection 차단
    • SI2. 중앙 Identifier Allowlist API
    • SI3. LIKE Wildcard Escape 강화
    • SI4. IN() 동적 Query 안전 생성
    • SI5. LIMIT / OFFSET 범위 검증
    • SI6. DB 오류정보 외부 노출 차단
    • SI7. WAF SQL Injection 패턴 강화
    • SI8. Second-Order SQL Injection 탐지
    • SI9. 보안 감사 로그
  6. 전체 방어 흐름
  7. 보안 로그 읽는 법
  8. 보안 강점 요약 — 기업 납품 요구사항 준수 현황
  9. 방어하지 못하는 영역
  10. 개발자 API 가이드
  11. Allowlist 커스터마이징
  12. 자주 묻는 질문


1. SQL Injection이란 무엇인가

공격자가 사용자 입력을 통해 SQL 구조 자체를 변경하여 데이터베이스를 무단 조작하는 공격입니다.


핵심 원칙

사용자 입력
     ↓
Application
     ↓
Database Layer
     ↓
SQL Statement

어느 단계에서도 사용자 입력이 SQL 구조로 승격되어서는 안 됩니다.


공격 유형

① Classic Injection: ' OR '1'='1
   SELECT * FROM users WHERE id = '' OR '1'='1'
   → 모든 사용자 반환

② Union-based: ' UNION SELECT user,password FROM admins--
   → 관리자 계정 탈취

③ Boolean-based: ?id=1 AND 1=1 (참) vs ?id=1 AND 1=2 (거짓)
   → 응답 차이로 데이터 추출

④ Time-based: ?id=1 AND SLEEP(5)
   → 응답 지연으로 블라인드 추출

⑤ ORDER BY Injection: ?sort=id,(SELECT 1 FROM users WHERE ...)
   → 정렬 컬럼에 서브쿼리 삽입

⑥ Second-Order: DB 저장 후 관리자 기능에서 폭발
   → 1단계: nickname = "admin'-- " 저장
   → 2단계: 관리자 검색에서 동적 SQL 생성 시 폭발


Prepared Statement가 왜 최선인가

취약한 방식:
  "SELECT * FROM users WHERE id = " + $_GET['id']
  → 공격자 입력: ?id=1 OR 1=1
  → 실행: SELECT * FROM users WHERE id = 1 OR 1=1
  → 모든 사용자 반환

Prepared Statement:
  "SELECT * FROM users WHERE id = ?"
  → 파라미터: [1 OR 1=1]  (문자열로 처리)
  → 실행: WHERE id = '1 OR 1=1'  (리터럴 값으로만 처리)
  → 일치하는 레코드 없음

SQL 구조와 데이터를 완전히 분리하기 때문에
어떤 입력값이 들어와도 SQL 구조가 변경되지 않습니다.


2. DXCMS 코어의 현재 SQL 보안 수준

코어 Database.php를 직접 분석한 결과입니다.


코어 Database.php 구조

// 모든 DB 호출의 최종 실행점
private function execute($sql, $params = array())
{
    $stmt = $this->pdo->prepare($sql);    // ← Prepared Statement
    $stmt->execute((array)$params);        // ← 파라미터 바인딩
    return $stmt;
}


코어가 이미 완벽하게 하는 것:

메서드 방어 방식
row($sql, $params) PDO Prepared Statement
rows($sql, $params) PDO Prepared Statement
query($sql, $params) PDO Prepared Statement
insert($sql, $params) PDO Prepared Statement
insertRow($table, $data) 컬럼명 Backtick + 값 바인딩
updateRow($table, $data, $where) 컬럼명 Backtick + 값 바인딩
deleteRow($table, $where) 컬럼명 Backtick + 값 바인딩
find(), exists(), count() 모두 Prepared Statement
tableExists() 정규식 필터 [^a-zA-Z0-9_]


boards/handler.php도 안전하게 구현:

// 정렬 필드 처리 — Allowlist 방식
if ($sf === 'like') {
    $orderBy = 'p.like_count DESC, p.id DESC';  // 고정 문자열
} elseif ($sf === 'popular') {
    $orderBy = 'p.popular_score DESC, p.id DESC'; // 고정 문자열
} else {
    $orderBy = 'p.id DESC';  // 기본 고정 문자열
}

// LIKE 검색 — 파라미터 바인딩
$where .= " AND p.`title` LIKE ?";
$params[] = '%' . $search . '%';

// IN() — 동적 placeholder
$ph = implode(',', array_fill(0, count($catNames), '?'));
$where .= " AND p.`category` IN ({$ph})";

코어 SQL 보안 자체 평가: 9/10 — 매우 우수



3. 코어가 막지 못하는 것 — 이 플러그인의 역할

❌ 미비점 1: Database::findAll()의 $order 파라미터

// Database.php findAll() 실제 코드
public function findAll($table, $where, $fields = '*', $order = '', $limit = '')
{
    // ...
    if ($order) $sql .= " ORDER BY {$order}";  // ← 직접 삽입!
    if ($limit) $sql .= " LIMIT {$limit}";      // ← 직접 삽입!
}

이 메서드를 사용하는 플러그인에서 $order에 사용자 입력을 넣으면:

// 위험한 플러그인 코드
$db->findAll('posts', [], '*', $_GET['sort']);
// ?sort=id,(SELECT 1 FROM admins WHERE 1=1)


❌ 미비점 2: LIKE wildcard Escape 없음

// 코어 게시판 검색
$params[] = '%' . $search . '%';
// ?q=%% → WHERE title LIKE '%%%'
//          → 전체 레코드 반환 (인덱스 무시, 전체 테이블 스캔)
// ?q=%a% → LIKE '%%a%%' → 의도치 않은 광범위 검색


❌ 미비점 3: WAF SQL 패턴이 2개뿐

// 코어 WAF 패턴
'/(union\s+all\s+select|select\s+.*\s+from\s+|information_schema)/i',
'/(into\s+(outfile|dumpfile)|load_file|benchmark\s*\(|sleep\s*\()/i',

우회 예시:

SELECT%09FROM → 탭 문자로 공백 대체
un/**/ion → 주석으로 키워드 분할
0x41444d494e → Hex 인코딩


❌ 미비점 4: DX_DEBUG=true 시 SQL 전문 노출

// Database.php execute() 오류 처리
if ($debugOn) {
    dx_error('DB 오류: ' . $e->getMessage() . ' | SQL: ' . $sql);
    //                                                   ↑ SQL 전문 노출
}

개발 설정이 운영 환경에 적용될 경우 공격자가 SQL 구조와 테이블명을 확인할 수 있습니다.


❌ 미비점 5: Second-Order SQLi 탐지 없음

DB에서 꺼낸 값이 동적 SQL에 삽입될 때 별도 검증이 없습니다.


4. 설치 방법

4-1. 파일 배치

(DXCMS 루트)/
└── plugins/
    └── dx-security-sqli/
        ├── manifest.php
        └── plugin.php


4-2. 관리자 페이지에서 활성화

관리자 → 플러그인 → dx-security-sqli → 활성화


5. 방어층 상세 설명


SI1. Database::findAll() ORDER BY Injection 차단

문제

Database::findAll()$order$limit 파라미터는 SQL에 직접 삽입됩니다. 플러그인 개발자가 이 파라미터에 사용자 입력을 넣으면 ORDER BY Injection이 발생합니다.


해결

dx_before_db_find_all 훅에서 ORDER BY 절을 검증합니다.

ORDER BY 절 허용 패턴:
  [a-zA-Z0-9_.\s,]+(ASC|DESC)
  예: "p.created_at DESC" → 통과
  예: "id,(SELECT ...)" → 차단 → 기본값 "id DESC"로 교체


SI2. 중앙 Identifier Allowlist API

SQL에서 파라미터 바인딩이 불가능한 영역:

컬럼명: ORDER BY column    (? 바인딩 불가)
테이블명: FROM table       (? 바인딩 불가)
방향: ORDER BY col ASC/DESC (? 바인딩 불가)

이 영역은 반드시 Allowlist로 검증해야 합니다.


dx_sqli_identifier() — 핵심 함수

/**
 * alias(사용자 입력) → SQL Identifier(안전한 컬럼명) 변환
 * 허용 목록에 없으면 기본값 반환
 */
$col = dx_sqli_identifier('post_order', $_GET['sort'], 'p.id');


Allowlist 정의 (plugin.php에서 확장 가능)

'post_order' Allowlist:
  'id''p.id'           (사용자 입력 'id'SQL 컬럼 'p.id')
  'date''p.created_at'   (사용자 입력 'date'SQL 컬럼 'p.created_at')
  'hit''p.hit_count'
  'like''p.like_count'
  'popular''p.popular_score'

이외 모든 입력 → 기본값 반환 + 로그

이 방식의 추가 장점: 실제 컬럼명을 사용자에게 노출하지 않습니다. (스키마 정보 보호)


dx_sqli_order() — ORDER BY 통합 함수

$orderBy = dx_sqli_order(
    'post_order',   // 컬럼 Allowlist 컨텍스트
    $_GET['sort'],  // 사용자 입력 컬럼
    $_GET['dir'],   // 사용자 입력 방향 (asc/desc)
    'p.id',         // 기본 컬럼
    'DESC'          // 기본 방향
);
$sql = "SELECT * FROM posts ORDER BY {$orderBy}";
// 결과: "ORDER BY p.id DESC" 또는 "ORDER BY p.created_at ASC" 등


SI3. LIKE Wildcard Escape 강화

문제

%_는 SQL LIKE 절에서 특수 의미를 가집니다.

%0개 이상의 임의 문자
_ → 정확히 1개의 임의 문자

공격:
  ?q=%WHERE title LIKE '%%%' → 전체 테이블 스캔 (성능 공격)
  ?q=_  → WHERE title LIKE '%_%'1자 이상 모든 레코드


dx_sqli_like() — LIKE Escape 함수

$keyword = dx_sqli_like($_GET['q']);
// 입력: "100%"   → "100\%"    (리터럴 % 검색)
// 입력: "name_1" → "name\_1"  (리터럴 _ 검색)
// 입력: "a\b"    → "a\\b"     (리터럴 \ 검색)

$db->row(
    "SELECT * FROM posts WHERE title LIKE ? ESCAPE '\\'",
    array('%' . $keyword . '%')
);


와일드카드 허용 모드

관리자 기능처럼 의도적으로 와일드카드를 허용해야 할 때:

$keyword = dx_sqli_like($_GET['q'], true);  // allowWildcard=true


SI4. IN() 동적 Query 안전 생성

코어 boards/handler.php는 이미 안전하게 처리합니다. 이 함수는 외부 플러그인 개발자를 위한 표준 API입니다.


위험한 방식

// 절대 사용 금지
$ids = implode(',', $_POST['ids']);
$db->query("DELETE FROM posts WHERE id IN ($ids)");
// ?ids[]=1&ids[]=2 DROP TABLE posts--


dx_sqli_in() — 안전한 IN() 생성

list($placeholders, $params) = dx_sqli_in($_POST['ids'], 'int', 100);
// 결과: $placeholders = "?,?,?"
//        $params = [1, 2, 3]  (음수/0 제거, 최대 100개 제한)

$db->query("DELETE FROM posts WHERE id IN ({$placeholders})", $params);


빈 배열 처리

list($ph, $params) = dx_sqli_in([], 'int');
// $ph = '0', $params = []
// WHERE id IN (0) → 결과 없음 (안전한 실패)
// WHERE id IN () → SQL 오류 발생 → 처리하지 않음


SI5. LIMIT / OFFSET 범위 검증

dx_sqli_limit() — LIMIT 안전 처리

$limit = dx_sqli_limit($_GET['per_page'], 20, 1, 100);
// ?per_page=-1   → 기본값 20 반환 (음수 차단)
// ?per_page=9999 → 기본값 20 반환 (최대 100 초과)
// ?per_page=abc  → 기본값 20 반환 (숫자 아님)
// ?per_page=50   → 50 반환 (정상)


dx_sqli_offset() — OFFSET 안전 처리

// 페이지 번호 모드 (기본)
$offset = dx_sqli_offset($_GET['page'], $limit, true);
// ?page=1 → offset=0
// ?page=2 → offset=20 (limit=20 기준)

// OFFSET 직접 모드
$offset = dx_sqli_offset($_GET['offset'], $limit, false);


SI6. DB 오류정보 외부 노출 차단

문제

// Database.php
if ($debugOn) {
    dx_error('DB 오류: ' . $e->getMessage() . ' | SQL: ' . $sql);
    //                                                   ↑ 노출!
}

DX_DEBUG=true가 운영 환경에 적용되면:

Error: DB 오류: Table 'dx_members' doesn't exist | SQL: SELECT * FROM dx_members WHERE id = ?
                 ↑ 테이블명 노출                          ↑ SQL 구조 노출

공격자는 이 정보로 정확한 테이블명과 SQL 구조를 파악할 수 있습니다.


이 플러그인의 해결

dx_extend_top 훅에서 운영 환경 판단:

개발 환경 (localhost, 127.0.0.1, *.local, dev.*):
  DX_DEBUG=true → 그대로 허용

운영 환경 (실제 도메인):
  DX_DEBUG=true → SQLI:DEBUG_ON_PRODUCTION 로그 기록
                  이후 DB 오류 시 "데이터베이스 오류가 발생했습니다." 만 출력
                  SQL 전문 · 테이블명 노출 없음


SI7. WAF SQL Injection 패턴 강화

코어 WAF 2가지 패턴에 15가지 패턴을 추가합니다.


추가 탐지 패턴

공격 유형 패턴 예시
Boolean-based OR 1=1, AND 'a'='a'
Stacked Queries ; DROP TABLE, ; SELECT
Error-based extractvalue(), updatexml()
주석 우회 --, #, /* */
Hex 인코딩 0x41, CHAR(65)
UNION 변형 UNION/**/SELECT, UNION(SELECT
서브쿼리 (SELECT 1 FROM users)
조건 함수 IF(), CASE WHEN, IFNULL()
Time-based 추가 WAITFOR DELAY, pg_sleep()
시스템 함수 user(), database(), version()
Out-of-band LOAD_DATA INFILE
공백 우회 탭(\t), 개행(\n) 사용
키워드 분할 sel/**/ect, un ion
인코딩 %27, %22 (URL 인코딩된 따옴표)


중요: WAF는 2차 방어

1차 방어: Prepared Statement (코어) → 완전한 방어
2차 방어: WAF 패턴 (이 플러그인) → 탐지·로그

WAF는 탐지 목적입니다.
WAF가 탐지했다고 요청을 차단하지 않으며
로그에만 기록하고 패턴을 분석합니다.
(과도한 차단은 정상 사용자 오탐 위험)


SI8. Second-Order SQL Injection 탐지

개념

1단계 — 저장:
  공격자가 nickname = "admin'--" 으로 가입
  → 입력 검증 통과 (파라미터 바인딩으로 안전하게 저장)

2단계 — 폭발:
  관리자 기능에서 nickname을 동적 SQL에 삽입:
  $sql = "SELECT * FROM logs WHERE user = '" . $user['nickname'] . "'";
  → SELECT * FROM logs WHERE user = 'admin'--'SQL 구조 변경 성공 (DB 저장 값이지만 안전하지 않음)


이 플러그인의 방어

dx_after_db_row 훅에서 주요 텍스트 필드의 값을 검사합니다.

탐지 필드: name, nickname, title, slug, key,
           setting_value, config, metadata

탐지 패턴:
  ' OR '1'='1      Boolean 기본
  '; DROP          다중 쿼리
  --               주석
  UNION SELECT     UNION 기반
  <?php            PHP 인젝션

탐지 시: SQLI:SECOND_ORDER_SUSPECTED 로그
         (실제 차단은 Prepared Statement가 담당)


근본적인 Second-Order 방어

가장 확실한 방어는 DB에서 꺼낸 값도 항상 Prepared Statement로 바인딩하는 것입니다.

// 잘못된 방식 (Second-Order 취약)
$user = $db->row("SELECT * FROM members WHERE id = ?", [$id]);
$sql = "SELECT * FROM logs WHERE user = '" . $user['name'] . "'";

// 올바른 방식 (항상 바인딩)
$user = $db->row("SELECT * FROM members WHERE id = ?", [$id]);
$logs = $db->rows("SELECT * FROM logs WHERE user = ?", [$user['name']]);


6. 전체 방어 흐름

HTTP 요청
      ↓
[SI7] WAF 패턴 검사 (GET/POST/COOKIE)
      ↓ 탐지 시 로그 기록 (차단 아님)

[SI6] DX_DEBUG 운영환경 검사
      ↓ 운영 환경이면 DB 오류 정보 보호

      ↓ 라우팅 + 컨트롤러 실행

[코어] Prepared Statement (1차 방어 — 가장 중요)
  모든 row(), rows(), query() 등 → PDO 바인딩

[SI2] dx_sqli_identifier() — ORDER BY / Column
  사용자 입력 → Allowlist 검증 → 안전한 SQL Identifier

[SI3] dx_sqli_like() — LIKE 검색
  검색어 → % _ \ EscapeLIKE 절에 안전 삽입

[SI4] dx_sqli_in() — IN() 절
  배열 입력 → 타입 검증 + 크기 제한 → 동적 placeholder

[SI5] dx_sqli_limit/offset() — 페이지네이션
  숫자 입력 → 범위 검증 → LIMIT / OFFSET

      ↓ DB 실행

[SI8] Second-Order 탐지
  DB 조회 값 → 위험 패턴 검사 → 로그 기록

      ↓ 응답


7. 보안 로그 읽는 법

data/security.log에 기록됩니다.


이벤트 타입

타입 의미
SQLI:WAF_PATTERN_DETECTED SQL Injection 의심 패턴 탐지
SQLI:IDENTIFIER_NOT_ALLOWED Allowlist에 없는 Identifier 입력
SQLI:IDENTIFIER_UNKNOWN_CONTEXT 정의되지 않은 Allowlist 컨텍스트
SQLI:ORDER_BY_UNSAFE_COLUMN 안전하지 않은 ORDER BY 컬럼
SQLI:FINDALL_ORDER_INJECTION findAll() ORDER BY Injection 시도
SQLI:SEARCH_KEYWORD_TOO_LONG 검색어 길이 초과 (200자)
SQLI:SECOND_ORDER_SUSPECTED Second-Order SQLi 의심 값 탐지
SQLI:LIMIT_OUT_OF_RANGE LIMIT 범위 초과
SQLI:OFFSET_OUT_OF_RANGE OFFSET 범위 초과
SQLI:IN_QUERY_TOO_LARGE IN() 파라미터 개수 초과
SQLI:DEBUG_ON_PRODUCTION 운영 환경에서 DX_DEBUG=true
SQLI:UNSAFE_TABLE_NAME 안전하지 않은 테이블명
SQLI:TABLE_NOT_ALLOWED Allowlist에 없는 테이블명


로그 예시

# SQL Injection 시도 탐지
[2026-09-01 09:00:01][SQLI:WAF_PATTERN_DETECTED][IP:45.33.x.x][...][UID:0][URI:/board/free]
source=GET key=q len=45

# 허용되지 않은 정렬 컬럼 시도
[2026-09-01 14:22:10][SQLI:IDENTIFIER_NOT_ALLOWED][IP:58.29.x.x][...][UID:5][URI:/board/list]
context=post_order input=(SELECT 1 FROM admins) fallback=p.id

# LIMIT 범위 초과
[2026-09-01 16:45:03][SQLI:LIMIT_OUT_OF_RANGE][IP:121.131.x.x][...][UID:42][URI:/api/posts]
input=99999 val=99999

# Second-Order 의심
[2026-09-01 20:11:44][SQLI:SECOND_ORDER_SUSPECTED][IP:103.21.x.x][...][UID:1][URI:/admin/member]
context=members.nickname pattern=' OR value_hash=a3f29c11

# 운영 환경 DEBUG 활성화 경고
[2026-09-01 09:00:00][SQLI:DEBUG_ON_PRODUCTION][IP:121.131.x.x][...][UID:0][URI:/]
DX_DEBUG=true on production host=example.com — SQL details will not be exposed

⚠️ SQL 전문·파라미터·패스워드는 절대 로그에 기록하지 않습니다.



8. 보안 강점 요약 — 기업 납품 요구사항 준수 현황

SQL 공격 테스트 결과

공격 유형 결과 방어 레이어
' OR '1'='1 ✅ 방어 코어 Prepared Statement
' UNION SELECT password FROM admins-- ✅ 방어 코어 Prepared Statement
'; DROP TABLE posts-- ✅ 방어 코어 Prepared Statement
?sort=id,(SELECT 1) ✅ 방어 SI2 Identifier Allowlist
?q=%% (전체 스캔) ✅ 방어 SI3 LIKE Escape
?ids[]=1&ids[]=DROP TABLE ✅ 방어 SI4 IN() 타입 검증
?page=-1 ✅ 방어 SI5 OFFSET 검증
DB 오류 SQL 노출 ✅ 방어 SI6 운영환경 감지
Boolean-based Blind ✅ 방어 코어 PS + SI7 WAF 탐지
Time-based Blind ✅ 방어 코어 PS + SI7 WAF 탐지
Second-Order ✅ 탐지+경고 SI8 (차단은 PS가 담당)


SQL-001~010 기업 요구사항

요구사항 내용 코어 이 플러그인
SQL-001 SQL 구조-데이터 완전 분리 ✅ PS
SQL-002 Prepared Statement 강제 ✅ 전체 적용
SQL-003 동적 Identifier Allowlist ✅ SI2
SQL-004 ORDER BY / GROUP BY 보호 △ (일부) ✅ SI1, SI2
SQL-005 LIKE Injection 방어 ❌ Escape 없음 ✅ SI3
SQL-006 IN() 동적 Query 방어 ✅ (코어) ✅ SI4 표준 API
SQL-007 LIMIT / OFFSET 방어 ✅ SI5
SQL-008 Second-Order SQLi 방어 ✅ SI8 탐지
SQL-009 Plugin SQL 감사 ✅ WAF+Allowlist
SQL-010 DB 최소권한 설정 의존 ✅ 가이드 제공
SQL-012 DB 오류 정보 차단 △ DEBUG 의존 ✅ SI6


9. 방어하지 못하는 영역

취약점 이유 추가 대책
DB 계정 최소권한 애플리케이션 외부 설정 DB 계정에서 DROP/ALTER/CREATE 제거
ORM/쿼리빌더 우회 써드파티 라이브러리 라이브러리별 보안 설정
NoSQL Injection 다른 DB 유형 MongoDB 등 사용 시 별도 방어
Time-based 완전 차단 응답 시간 기반은 탐지 어려움 DB 레벨 쿼리 타임아웃 설정


10. 개발자 API 가이드

ORDER BY 안전 처리

// 플러그인에서 사용자 입력 정렬
$orderBy = dx_sqli_order(
    'post_order',     // Allowlist 컨텍스트
    $_GET['sort'],    // 사용자 입력
    $_GET['dir'],     // 방향
    'p.id',           // 기본 컬럼
    'DESC'            // 기본 방향
);
$db->rows("SELECT * FROM {$db->table('posts')} ORDER BY {$orderBy}", []);


LIKE 검색

$keyword = dx_sqli_like($_GET['q']);  // % _ \ Escape
$db->rows(
    "SELECT * FROM posts WHERE title LIKE ? ESCAPE '\\'",
    ['%' . $keyword . '%']
);


IN() 복수 삭제

list($ph, $params) = dx_sqli_in($_POST['ids'], 'int', 50);
if (!empty($params)) {
    $db->query("DELETE FROM posts WHERE id IN ({$ph})", $params);
}


페이지네이션

$limit  = dx_sqli_limit($_GET['per'], 20);   // 1~1000, 기본 20
$offset = dx_sqli_offset($_GET['page'], $limit); // 페이지 → offset
$db->rows("SELECT * FROM posts LIMIT ? OFFSET ?", [$limit, $offset]);


커스텀 Allowlist 등록

// 플러그인 초기화 시
dx_sqli_allowlist_add('my_plugin_order', array(
    'price'  => 'p.price',
    'stock'  => 'p.stock_count',
    'rating' => 'p.avg_rating',
));

// 사용
$col = dx_sqli_identifier('my_plugin_order', $_GET['sort'], 'p.id');


안전한 동적 테이블 (플러그인용)

// 허용된 테이블만 사용
$table = dx_sqli_safe_table(
    $_GET['board'],
    ['posts', 'comments', 'files']  // Allowlist
);
if (!$table) {
    dx_error('허용되지 않은 테이블입니다.', 400);
}
$db->rows("SELECT * FROM `{$table}` WHERE status = ?", [1]);


11. Allowlist 커스터마이징

plugin.php$GLOBALS['_dx_sqli_identifier_allowlist']에 새 컨텍스트를 추가합니다.

// plugin.php 상단의 Allowlist에 추가
$GLOBALS['_dx_sqli_identifier_allowlist']['shop_order'] = array(
    'price'    => 'p.price',
    'date'     => 'p.created_at',
    'rating'   => 'p.rating',
    'popular'  => 'p.sales_count',
);

// 사용
$col = dx_sqli_identifier('shop_order', $_GET['sort'], 'p.created_at');


12. 자주 묻는 질문

Q. 코어가 이미 Prepared Statement를 쓰는데 추가 보안이 왜 필요한가요?

Prepared Statement는 **값(Value)**을 안전하게 바인딩합니다. 그러나 **Identifier(컬럼명, 테이블명, 정렬 방향)**는 바인딩이 불가능합니다. findAll()$order 파라미터처럼 Identifier가 직접 SQL에 삽입되는 경우가 존재하며, 플러그인 개발자가 이를 잘못 사용할 가능성이 있습니다.


Q. WAF 패턴이 정상 검색어를 차단하지 않나요?

이 플러그인의 WAF는 차단하지 않고 로그만 기록합니다. --가 포함된 파일명이나 %가 들어간 검색어 등 정상 내용이 패턴에 매치되더라도 요청은 계속 처리됩니다. Prepared Statement가 실제 방어를 담당하므로 WAF 오탐이 서비스에 영향을 주지 않습니다.


Q. dx_sqli_like()를 사용하면 % 검색이 안 되지 않나요?

dx_sqli_like($keyword, false) (기본)는 %를 리터럴로 처리합니다. 사용자가 100%를 검색하면 정확히 "100%" 문자열을 검색합니다. 관리자 기능처럼 와일드카드가 필요하면 dx_sqli_like($keyword, true)를 사용하세요.


Q. Second-Order SQLi 탐지가 발동해도 실제로 공격이 차단되나요?

탐지 시 로그를 기록하고 경고합니다. 실제 차단은 DB 접근 시 Prepared Statement가 담당합니다. Second-Order 공격이 성공하려면 DB에서 꺼낸 값을 동적 SQL에 직접 연결해야 하는데, 코어 Database.php를 사용하는 한 모든 값이 파라미터로 바인딩되므로 공격이 실패합니다.


DX Security — SQL Injection 방어 v1.0.0 — DesignOneX
https://designonex.com
 

라이선스

디자인원엑스 라이선스 참고
https://designonex.com/notice/view/1787577858493247

첨부파일 1개 회원전용

댓글0

로그인 후 댓글을 작성할 수 있습니다.
Plugin 49
45
전체 회원
1,375
전체 게시글
2,737
전체 댓글
37
오늘 방문
53,475
전체 방문
1
현재 접속
인기글 7일 이내
최신글
최신댓글
내 플레이리스트
플레이리스트가 비어있습니다
스튜디오 게시판에서
플레이리스트에 담기 버튼을
눌러보세요
목록
목록