大吉大利杯web部分wp
菜炸了,一部分题是自己做出来的,一部分题是看wp复现的
spaceman
php代码审计,源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| <?php
error_reporting(0);
highlight_file(__FILE__);
class spaceman
{
public $username;
public $password;
public function __construct($username,$password)
{
$this->username = $username;
$this->password = $password;
}
public function __wakeup()
{
if($this->password==='ctfshowvip')
{
include("flag.php");
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
function filter($string){
return str_replace('ctfshowup','ctfshow',$string);
}
$str = file_get_contents("php://input");
if(preg_match('/\_|\.|\]|\[/is',$str)){
die("I am sorry but you have to leave.");
}else{
extract($_POST);
}
$ser = filter(serialize(new spaceman($user_name,$pass_word)));
$test = unserialize($ser);
?>
|
php://input 与 $_POST
先说说php://input传参的问题,当直接poststr=xxxx
的时候可以直接传入变量str,但是当poststr=xxx&pass_word=xxx
的时候,php://input会将这一整串赋值给str,因为其中有下划线,所以无法绕过,测试如下:
上网查找相关资料,发现:
1
2
3
| 1.Coentent-Type仅在取值为application/x-www-data-urlencoded和multipart/form-data两种情况下,PHP才会将http请求数据包中相应的数据填入全局变量$_POST
2.只有Coentent-Type为multipart/form-data的时候,PHP不会将http请求数据包中的相应数据填入php://input,否则其它情况都会。
|
也就是说,Coentent-Type为multipart/form-data时,php://input为空而$_POST不为空,从而绕过该正则。至于如何传呢,我是使用插件Tabbed Postman - REST Client
,其他的方法我也不会(菜)
反序列化
刚开始觉得filter函数吞掉了俩字符,会导致反序列无法正常进行,后面发现,这个filter函数过滤了个寂寞。。。
正常情况下压根儿就没有ctfshowup
给他进行替换。。。所以直接以form-data,post一个pass_word=ctfshowvip即可,user_name随便填
可能是出题没想到这个非预期吧
veryphp
也是一道php代码审计,源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| <?php
error_reporting(0);
highlight_file(__FILE__);
include("config.php");
class qwq
{
function __wakeup(){
die("Access Denied!");
}
static function oao(){
show_source("config.php");
}
}
$str = file_get_contents("php://input");
if(preg_match('/\`|\_|\.|%|\*|\~|\^|\'|\"|\;|\(|\)|\]|g|e|l|i|\//is',$str)){
die("I am sorry but you have to leave.");
}else{
extract($_POST);
}
if(isset($shaw_root)){
if(preg_match('/^\-[a-e][^a-zA-Z0-8]<b>(.*)>{4}\D*?(abc.*?)p(hp)*\@R(s|r).$/', $shaw_root)&& strlen($shaw_root)===29){
echo $hint;
}else{
echo "Almost there."."<br>";
}
}else{
echo "<br>"."Input correct parameters"."<br>";
die();
}
if($ans===$SecretNumber){
echo "<br>"."Congratulations!"."<br>";
call_user_func($my_ans);
}
|
php://input 与 $_POST
传参问题与上一题一样,这里就不再赘述了。
正则
绕了半天不知道这个正则想匹配些啥(可能是我太菜了),不过最后总算是绕过去了,学习正则的话可以参考这篇文章:正则表达式30分钟入门教程,最后给出$shaw_root的值:-a9<b>>>>>abcaaaaaaaa.php@Rr.
。
$SecretNumber
绕过正则之后得到一个hint:Here is a hint : md5("shaw".($SecretNumber)."root")==166b47a5cb1ca2431a0edfcef200684f && strlen($SecretNumber)===5
,感觉出题人是想让我们根据这个hint把SecretNumber爆出来,但是这里两个变量都可控,应该是被非预期了。。。
call_user_func
又是一个变量的call_user_func。。。上回我盯着phpinfo看了大半天。。。这次也是先传入一个phpinfo找找,但是并没发现什么可疑的东西,然后又搜了下flag,也没找到。然后这时候想起最前面的qwq类以及其静态方法,刚开始直接传入my_ans=oao,但是发现行不通。。。于是去Google了一下call_user_func如何调用类的静态方法:
因为oao函数没有参数,所以可以传入my_ans数组从而调用该静态方法,最后payload:
虎山行&revenge
这题真的不会,之前也没有做过phar的反序列化,基本上都是靠着各种wp和资料复现的。
打开之后有个hint:
访问之后:
安装看看:
假装自己扫了一下目录:
拿到源码之后先拿d盾扫一下:
好像没啥用,后面同学和我说可以传file直接目录穿越(审计源码能力太差了):
然后打开:/ctfshowsecretfilehh:
利用/mc-admin/page-edit.php?file=../../../../../../../var/www/html/ctfshowsecretfilehh/waf.php
读取waf.php源码:
1
2
3
4
5
6
7
8
| <?php
function waf($file){
if (preg_match("/^phar|smtp|dict|zip|compress|file|etc|root|filter|php|flag|ctf|hint|\.\.\//i",$file)){
die("姿势太简单啦,来一点骚的?!");
}else{
return $file;
}
}
|
再用/mc-admin/page-edit.php?file=../../../upload.php
读取upload.php的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| <?php
error_reporting(0);
// 允许上传的图片后缀
$allowedExts = array("gif", "jpg", "png");
$temp = explode(".", $_FILES["file"]["name"]);
// echo $_FILES["file"]["size"];
$extension = end($temp); // 获取文件后缀名
if ((($_FILES["file"]["type"] == "image/gif")
|| ($_FILES["file"]["type"] == "image/jpeg")
|| ($_FILES["file"]["type"] == "image/png"))
&& ($_FILES["file"]["size"] < 2048000) // 小于 2000kb
&& in_array($extension, $allowedExts))
{
if ($_FILES["file"]["error"] > 0)
{
echo "文件出错: " . $_FILES["file"]["error"] . "<br>";
}
else
{
if (file_exists("upload/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " 文件已经存在。 ";
}
else
{
$md5_unix_random =substr(md5(time()),0,8);
$filename = $md5_unix_random.'.'.$extension;
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename);
echo "上传成功,文件存在upload/";
}
}
}
else
{
echo "文件类型仅支持jpg、png、gif等图片格式";
}
?>
|
这里直接参考yu22x师傅博客里的脚本找上传的文件名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| import requests
import time
import hashlib
def geturl(s):
a=hashlib.md5(str(int(s)).encode()).hexdigest()
return "http://b8876a9e-71b3-4a02-b8c4-80f02ec058a5.chall.ctf.show/upload/"+a[:8]+'.gif'
#coding:utf-8
while True:
url="http://b8876a9e-71b3-4a02-b8c4-80f02ec058a5.chall.ctf.show/upload.php"
files={'file':('exp.gif',
open('exp.gif','rb'),
'image/gif'
)}
r=requests.post(url,files=files)
print(r.text)
a=time.time()
r2=requests.get(geturl(a))
r3=requests.get(geturl(a-1))#存在延时多试几个时间
if("我的网站" not in r2.text or "我的网站" not in r3.text):
print(geturl(a))
print(geturl(a-1))
break
else:
time.sleep(0.3)
|
先在/uplaod上传一个phar文件(后缀名已改为gif),然后再用该脚本找文件名:
然后访问hint.txt:
访问/ctfshowgetflaghhhh:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php
show_source(__FILE__);
$unser = $_GET['unser'];
class Unser {
public $username='Firebasky';
public $password;
function __destruct() {
if($this->username=='ctfshow'&&$this->password==(int)md5(time())){
system('cp /ctfshow* /var/www/html/flag.txt');
}
}
}
$ctf=@unserialize($unser);
system('rm -rf /var/www/html/flag.txt');
|
因为md5之后有一个强制类型转换,所以当md值是字母开头的时候会变成0,所以我们可以令password为0,然后多试几次既可:
后面有个system('rm -rf /var/www/html/flag.txt');
删掉flag,理论上需要条件竞争,但是不知道为啥狂点了几次hackbar的EXECUTE之后就可以直接url访问。。。。迷
revenge只有路由的名字不一样,不知道有啥意义。。。迷