Skip to content

N1CTF2021 Web Writeup

前言

Tldr

  1. 耐心查阅官方文档乃至源码
  2. 勤动手 Debug
  3. 有意识地造脚本

主要想推荐下第一点,后两点是反思下自己老烦的毛病,就不展开了(忘了原本想写什么了

这场时间有点尴尬,第一天西湖论剑 8h 线上赛,第二天坐车过去开新生见面会。

基本是深夜上号,肝最久的是 tornado,摸了最后大半夜文档,差点就出了有点可惜

这里又不得不提到亲爱的 cg,之前看到一篇总结的还行的 Flask SSTI 的时候,顺势发现 引用了他的这篇 深入SSTI-从NCTF2018两道Flask看bypass新姿势,姿势确实涨知识,但我觉得最重要的是 cg 讲的查官方文档的意识。

的层面上来讲,互联网能获取的资源可太多了,各种 SSTI 总结讲解和 payload 及 bypass 技巧,Flask 已经被玩出花了,护网杯 2018 也出过一道 tornado 利用 handler.settings

不过想走的更远还是得讲

Missing

占坑待续

tornado

题目给出信息 python 3.9.7, tornado 6.1

app.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import tornado.ioloop
import tornado.web
import builtins
import unicodedata
import uuid
import os
import re

def filter(data):
    data = unicodedata.normalize('NFKD',data)
    if len(data) > 1024:
        return False
    if re.search(r'__|\(|\)|datetime|sys|import',data):
        return False
    for k in builtins.__dict__.keys():
        if k in data:
            return False
    return True

class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("templates/index.html",)
    def post(self):
        data = self.get_argument("data")
        if not filter(data):
            self.finish("no no no")
        else:
            id = uuid.uuid4()
            f = open(f"uploads/{id}.html",'w')
            f.write(data)
            f.close()
            try:
                self.render(f"uploads/{id}.html",)
            except:
                self.finish("error")
            os.unlink(f"uploads/{id}.html")

def make_app():
    return tornado.web.Application([
        (r"/", IndexHandler),
    ],compiled_template_cache=False)

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

测试过程用的脚本,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests

url = 'http://82.157.43.100:5000/'

# {% include ../../../etc/passwd %}
# {{request}}
tp = """\
{% try %}
  {% if 2 > 1 %}
    {% for i in ['se'+'ttings'] %}
      {% raw handler %}
      {% include ../templates/index.html %}
    {% end %}
  {% end %}
{% except %}
{% end %}
"""

payload = {'data': tp}

r = requests.post(url=url, data=payload, 
                  headers={'X-Forwarded-For': '123', 'X-Real-Ip': '123'})

print(r.text)

这里有个小困惑是,打印 request 的时候 remote_ip 始终显示的是我的真实 ip,不过从文档来看

Quote

Client’s IP address as a string. If HTTPServer.xheaders is set, will pass along the real IP address provided by a load balancer in the X-Real-Ip or X-Forwarded-For header.

这里应该可以伪造一手的

Missing

占坑待续

signin

literally signin,

源码很清晰的告诉我们要做什么,

官方 wp 出处其实也就是 php 文档

利用那个 urldecode 函数绕过 date 也是可以的

easyphp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
❯ tree
.
├── Dockerfile
├── app
│   ├── flag.php
│   ├── index.php
│   ├── log
│   └── log.php
├── configs
│   ├── app.conf
│   ├── nginx.conf
│   └── www.conf
└── supervisord.conf

3 directories, 8 files

翻了下题意还是在引导我们玩 php 的,配置就不考虑了

Source

index.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
include_once "flag.php";
include_once "log.php";

if(file_exists(@$_GET["file"])){
    echo "file exist!";
}else{
    echo "file not exist!";
}

?>

log.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
define('ROOT_PATH', dirname(__FILE__));

$log_type = @$_GET['log_type'];
if(!isset($log_type)){
    $log_type = "look";
}

$gets = http_build_query($_REQUEST);

$real_ip = $_SERVER['REMOTE_ADDR'];
$log_ip_dir = ROOT_PATH . '/log/' . $real_ip;

if(!is_dir($log_ip_dir)){
    mkdir($log_ip_dir, 0777, true);
}

$log = 'Time: ' . date('Y-m-d H:i:s') . ' IP: [' . @$_SERVER['HTTP_X_FORWARDED_FOR'] . '], REQUEST: [' . $gets . '], CONTENT: [' . file_get_contents('php://input') . "]\n";
$log_file = $log_ip_dir . '/' . $log_type . '_www.log';

file_put_contents($log_file, $log, FILE_APPEND);

?>

flag.php

1
2
3
4
5
6
7
8
<?php

CLASS FLAG {
    private $_flag = 'n1ctf{************************}';
    public function __destruct(){
        echo "FLAG: " . $this->_flag;
    } 
}

这里搜了一下才想起 file_exists 可以打 phar,洞很明显,就看怎么利用 log 来构造

回去再翻翻 phar 的利用条件以及构造方法就可以知道,生成 phar 文件会有其特征头,并且会计算签名校验,我们要做的就是参照 log.php 的日志记录格式,来拼接出一个符合条件可被 phar 协议识别的日志文件

Missing

占坑待续

QQQueryyy_all_the_things

Missing

待复现——瞟了一眼,约等于没看

funny_web

Missing

待复现——没看

Back to top