관리 메뉴

웹개발자의 기지개

[ASP] 파일 다운로드 예제 - SQL injection 방지 본문

ASP

[ASP] 파일 다운로드 예제 - SQL injection 방지

웹개발자 워니 2025. 9. 26. 20:44

 

[ 기본 소스 ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%
Option Explicit
 
Dim dir,filename,tb
tb            = Request.QueryString("tb")
filename    = Request.QueryString("filename")
 
response.Clear
response.ContentType = "application/unknown"
response.AddHeader "Pragma","no-cache"
response.AddHeader "Expires","0"
response.AddHeader "Content-Transfer-Encoding","binary"
response.AddHeader "Content-Disposition","attachment;filename=" & filename
 
Dim objStream
Set objStream = Server.CreateObject("ADODB.Stream")
objStream.Open
 
 
Dim TmpPath,strDirectory
TmpPath = "..\pds\" & tb & "\_files"
strDirectory = server.MapPath(TmpPath) & "\" & filename
 
objStream.Type = 1
objStream.LoadFromFile strDirectory '절대경로
 
Response.BinaryWrite objStream.Read
Response.Flush
 
objStream.Close
Set objStream = Nothing
 
%>
 
cs

 

 

 

[ SQL 인젝션 소스 보완된 소스 ]

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<%
Folder = Request("dir")
SEQ = Request("SEQ")
FILENAME = Request("file")
 
'Response.write SEQ & "<br>"
'Response.write FILENAME
'Response.End
 
seq = Request.QueryString("SEQ")
If Not IsNumeric(seq) Then
  Response.Status = "400 Bad Request"
  Response.Write "Invalid parameter"
  Response.End
End If
 
' 파일명/디렉터리는 화이트리스트로 매핑하거나 DB에서 id->파일경로로 조회
dir = Request.QueryString("dir")
If InStr(1, dir, ".."> 0 Or dir = "" Then
  Response.Status = "400 Bad Request" : Response.End
End If
 
' 파일명도 허용된 패턴만 허용 (예: 알파넘 + 확장자)
'fileName = Request.QueryString("file")
'If Not (fileName Like "[A-Za-z0-9_%-]*.*") Then
'  Response.Status = "400 Bad Request" : Response.End
'End If
 
 
 
'-------------------------------
 
Dim fileNameDecoded, rawQS, fileEncoded, isOK
Dim re, matches, suspiciousPatternsEncoded, suspiciousPatternsDecoded
 
' 1) 파라미터 읽기
fileNameDecoded = ""
On Error Resume Next
fileNameDecoded = Request.QueryString("file")  ' 대부분의 경우 이 값은 이미 디코딩된 상태
On Error GoTo 0
 
If Len(Trim(fileNameDecoded)) = 0 Then
  Response.Status = "400 Bad Request"
  Response.Write "Missing file parameter"
  Response.End
End If
 
' 2) 기본 금지 검사: 경로 횡단, 슬래시, null 바이트 등
If InStr(fileNameDecoded, ".."> 0 Or InStr(fileNameDecoded, "/"> 0 Or InStr(fileNameDecoded, "\") > 0 Then
  Call BlockAndLog("Path traversal or slash in decoded filename", fileNameDecoded)
End If
 
If InStr(fileNameDecoded, Chr(0)) > 0 Then
  Call BlockAndLog("Null byte in decoded filename", fileNameDecoded)
End If
 
' 3) 길이 제한 (필요 시 조정)
If Len(fileNameDecoded) > 255 Then
  Call BlockAndLog("Filename too long", fileNameDecoded)
End If
 
' 4) 정규식으로 디코딩된 파일명 허용 패턴 검사
Set re = New RegExp
re.IgnoreCase = True
re.Global = False
' 허용: 영문숫자, 한글(가-힣), 공백, 밑줄 _, 하이픈 -, 소괄호 ( ), 마침표(.) 구분자, 마지막에 확장자(영숫자 1~10자)
re.Pattern = "^[A-Za-z0-9_\-\s가-힣()]+(\.[A-Za-z0-9_\-\s가-힣()]+)*\.[A-Za-z0-9]{1,10}$"
 
If Not re.Test(fileNameDecoded) Then
  Call BlockAndLog("Decoded filename failed whitelist regex", fileNameDecoded)
End If
 
' 5) 원본 쿼리(인코딩된 값)에서 file 파라미터의 '원문(인코딩된 문자열)' 추출
rawQS = Request.ServerVariables("QUERY_STRING")  ' 예: file=%ED%95%9C%EA%B8%80+file.pdf&SEQ=1
fileEncoded = GetRawQueryParamValue(rawQS, "file") ' 인코딩된 값 그대로 반환 (percent-encoded)
 
If Len(Trim(fileEncoded)) = 0 Then
  ' 만약 원본에서 찾지 못하면 로그로 남기고 차단
  Call BlockAndLog("Raw encoded file param missing", rawQS)
End If
 
' 6) 검사: 원본(인코딩된)에서 의심스러운 인코딩 시퀀스 탐지
'    - SQLi 관련 인코딩: %27 (') , %3B (;) , %7C (|), %2F (/) , %5C (\) 등
'    - 이중 인코딩: %25 (percent) 존재 => 페이로드이중인코딩 의심
suspiciousPatternsEncoded = Array("%27","%3B","%3b","%7C","%7c","%2F","%2f","%5C","%5c","%2E%2E","%2E%2E%2F","%25")  ' %25 포함 -> double-encoding
Dim i, patt
For i = 0 To UBound(suspiciousPatternsEncoded)
  patt = suspiciousPatternsEncoded(i)
  If InStr(LCase(fileEncoded), LCase(patt)) > 0 Then
    Call BlockAndLog("Suspicious encoded token in raw file param: " & patt, fileEncoded)
  End If
Next
 
' 7) 검사: 인코딩된 형태로 'update set' 등 SQL 키워드 인코딩 시도 탐지
'    예: 'update' percent-encoding = %75%70%64%61%74%65  — 공격 스캐너가 이렇게 보낼 수 있음
'    간단히 'update'의 인코딩 패턴을 찾아본다 (case-insensitive)
Dim encodedUpdate, encodedSelect
encodedUpdate = "%75%70%64%61%74%65"  ' 'update'
encodedSelect = "%73%65%6c%65%63%74"  ' 'select' (소문자 hex)
If InStr(LCase(fileEncoded), encodedUpdate) > 0 Or InStr(LCase(fileEncoded), encodedSelect) > 0 Then
  Call BlockAndLog("Encoded SQL keyword found (update/select)", fileEncoded)
End If
 
' 8) 추가 안전장치: 원본에 연속으로 긴 영숫자 기반의 Base64-like 문자열이 있는 경우(난독화 페이로드 의심)
Set re = New RegExp
re.IgnoreCase = True
re.Global = False
' 하나의 라인 안에서 길게 연결된 Base64 패턴(=로 끝남) 탐지
re.Pattern = "[A-Za-z0-9_\-]{120,}=%?|%[A-Za-z0-9]{120,}" ' 단순 탐지, 필요 시 조정
If re.Test(fileEncoded) Then
  Call BlockAndLog("Long base64-like string in encoded file param", fileEncoded)
End If
 
' 9) 통과하면 안전한 파일명으로 정리(공백 연속 정리 등)
fileNameDecoded = Trim(fileNameDecoded)
' 연속 공백을 하나로 (간단 반복)
Do While InStr(fileNameDecoded, "  ") > 0
  fileNameDecoded = Replace(fileNameDecoded, "  ", " ")
Loop
 
' 이제 fileNameDecoded을 실제로 사용하지 말고(권장) SEQ로 DB에서 안전한 경로를 조회해서 전송하세요.
' 예: safePath = GetPathBySeq(CInt(Request.QueryString("SEQ")))
' Response.TransmitFile safePath
'Response.Write "OK"   ' 임시(테스트) 응답
'Response.End
 
 
' ============================
' Helper: 원본 쿼리에서 파라미터 값(raw, percent-encoded) 추출
' ============================
Function GetRawQueryParamValue(rawQS, paramName)
  ' rawQS: Request.ServerVariables("QUERY_STRING")
  ' paramName: 예: "file"
  ' 반환: 퍼센트 인코딩 상태의 값 (예: %ED%95%9C%EA%B8%80+file.pdf)
  Dim regex, matches, pattern, val
  Set regex = New RegExp
  regex.IgnoreCase = True
  regex.Global = False
  ' param=... 패턴: 앰퍼샌드 또는 끝까지 캡처, URL 쿼리의 param값은 & 또는 끝으로 끝남
  pattern = "(?:^|&)" & paramName & "=([^&]*)"
  regex.Pattern = pattern
  Set matches = regex.Execute(rawQS)
  If matches.Count > 0 Then
    val = matches(0).SubMatches(0)
    GetRawQueryParamValue = val
  Else
    GetRawQueryParamValue = ""
  End If
End Function
 
' ============================
' Helper: 차단 및 로깅
' ============================
Sub BlockAndLog(reason, data)
  ' 로그에 남기기 (간단히 파일에 append)
  Dim fso, logPath, logMsg
  logPath = Server.MapPath("/file_download_logs/security_block.log") ' 존재하지 않으면 디렉토리/파일 미리 만들어 둘 것
  Set fso = Server.CreateObject("Scripting.FileSystemObject")
  On Error Resume Next
  If Not fso.FolderExists(Server.MapPath("/file_download_logs")) Then
    fso.CreateFolder(Server.MapPath("/file_download_logs"))
  End If
  Dim ts
  Set ts = fso.OpenTextFile(logPath, 8, True) ' ForAppending
  logMsg = Now() & " | BLOCK | " & Request.ServerVariables("REMOTE_ADDR") & " | Reason: " & reason & " | Data: " & Replace(data, vbCrLf, " ") & vbCrLf
  ts.WriteLine logMsg
  ts.Close
  Set ts = Nothing
  Set fso = Nothing
 
  ' 응답 차단
  Response.Status = "400 Bad Request"
  Response.Write "Invalid request"
  Response.End
End Sub
 
 
'-----------------------------------
 
 
 
If SEQ = "" Or FILENAME = "" Then
  Response.Status = "400 Bad Request"
  Response.Write "Invalid request"
  Response.End  
End If
 
filePath = Server.Mappath("/") & "/data/board/" & Folder & "/" & SEQ & "/" & FILENAME
FolderPath = Server.Mappath("/") & "/data/board/" & Folder & "/" & SEQ 
 
 
Set fso = Server.CreateObject ("Scripting.FileSystemObject")
    If Not fso.FolderExists(FolderPath) Then
        Response.Write "<script>location.href='/index.asp';</script>"
    Else
        Set stream = Server.CreateObject("ADODB.Stream")
            Response.Buffer = True
            Response.Expires = 0
            Response.Clear
 
            stream.Open
            stream.Type = 1
 
            Set f = fso.GetFile(filePath)
                fileLength = f.size
                stream.LoadFromFile(filePath)
                With Response
                    .ContentType = "application/octet-stream name=" & filePath
                    .AddHeader "Content-Disposition","attachment;filename=" & Trim(SERVER.UrlEncode(FILENAME))
                    .AddHeader "Content-Length" , fileLength
                    .CharSet = "utf-8"
                    .ContentType = "application/octet-stream"
                    strFile = stream.Read
                    .BinaryWrite strFile
                    .Flush
                End With
            Set f = Nothing
        Set stream = Nothing
    End If
Set fso = Nothing
%>
cs

 

 

소스가 길어서 복잡할 수 있는데, injection 공격에 좀더 보완한 소스이다.

게다기 이상한 접근이 있을때, 

/file_download_logs/security_block.log

폴더에 기록 로그가 남도록 해놓았다.

 

 

 

Comments