你好,我想问一下,为什么触发ssrf如果不带上自己cookie去访问的话,就写不进自己session里面
Team: Aurora
首先感谢L-CTF出题的师傅们为我们带来了一场精彩的CTF比赛,出题和运维的大佬们都辛苦了!
[TOC]
Misc
签到题
计算器算出来答案是-2
Web
bestphp's revenge
打开题目发现有点像2018Xctf-final决赛的一道题
首先这道题有一个回调函数,参数可控,session的内容也可控,同时扫描后台还发现了flag.php,如下
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;
}
only localhost can get flag!
题目开始之后给了个hint:反序列化。
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列化之后的内容。
php的默认是php引擎,所以我们想要利用,需要先把引擎修改为php_serialize。
从flag.php可以看到,想要把flag写进session,需要本地访问,这里想到ssrf,而之前暨南大学招新赛的一道web题中提到了soap导致的ssrf,这个soap这个内置类刚好符合我们这道题
于是思路就有了,通过session反序列化攻击,触发ssrf去访问flag.php页面,把flag写进session里面。但是这里注意到,触发ssrf是如果不带上自己cookie去访问的话,是写不进自己session里面,这里需要利用到soap+crlf。
下面是攻击过程
然后通过变量覆盖,回调函数让soap去调用welcome_to_the_lctf2018方法,不存在,去调用_call方法,触发ssrf,写入session,最终得到flag
T4lk 1s ch34p,sh0w m3 the sh31l
题目给了源码
<?php
$SECRET = `../read_secret`;
$SANDBOX = "../data/" . md5($SECRET. $_SERVER["REMOTE_ADDR"]);
$FILEBOX = "../file/" . md5("K0rz3n". $_SERVER["REMOTE_ADDR"]);
@mkdir($SANDBOX);
@mkdir($FILEBOX);
if (!isset($_COOKIE["session-data"])) {
$data = serialize(new User($SANDBOX));
$hmac = hash_hmac("md5", $data, $SECRET);
setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
}
class User {
public $avatar;
function __construct($path) {
$this->avatar = $path;
}
}
class K0rz3n_secret_flag {
protected $file_path;
function __destruct(){
if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){
die("Sorry Sorry Sorry");
}
include_once($this->file_path);
}
}
function check_session() {
global $SECRET;
$data = $_COOKIE["session-data"];
list($data, $hmac) = explode("-----", $data, 2);
if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac)){
die("Bye");
}
if ( !hash_equals(hash_hmac("md5", $data, $SECRET), $hmac) ){
die("Bye Bye");
}
$data = unserialize($data);
if ( !isset($data->avatar) ){
die("Bye Bye Bye");
}
return $data->avatar;
}
function upload($path) {
if(isset($_GET['url'])){
if(preg_match('/^(http|https).*/i', $_GET['url'])){
$data = file_get_contents($_GET["url"] . "/avatar.gif");
if (substr($data, 0, 6) !== "GIF89a"){
die("Fuck off");
}
file_put_contents($path . "/avatar.gif", $data);
die("Upload OK");
}else{
die("Hacker");
}
}else{
die("Miss the URL~~");
}
}
function show($path) {
if ( !is_dir($path) || !file_exists($path . "/avatar.gif")) {
$path = "/var/www";
}
header("Content-Type: image/gif");
die(file_get_contents($path . "/avatar.gif"));
}
function check($path){
if(isset($_GET['c'])){
if(preg_match('/^(ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file)(.|\\s)*/i',$_GET['c'])){
die("Hacker Hacker Hacker");
}else{
$file_path = $_GET['c'];
list($width, $height, $type) = @getimagesize($file_path);
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}else{
list($width, $height, $type) = @getimagesize($path."/avatar.gif");
die("Width is :" . $width." px<br>" .
"Height is :" . $height." px<br>");
}
}
function move($source_path,$dest_name){
global $FILEBOX;
$dest_path = $FILEBOX . "/" . $dest_name;
if(preg_match('/(log|etc|session|proc|root|secret|www|history|file|\.\.|ftp|php|phar|zlib|data|glob|ssh2|rar|ogg|expect|http|https)/i',$source_path)){
die("Hacker Hacker Hacker");
}else{
if(copy($source_path,$dest_path)){
die("Successful copy");
}else{
die("Copy failed");
}
}
}
$mode = $_GET["m"];
if ($mode == "upload"){
upload(check_session());
}
else if ($mode == "show"){
show(check_session());
}
else if ($mode == "check"){
check(check_session());
}
else if($mode == "move"){
move($_GET['source'],$_GET['dest']);
}
else{
highlight_file(__FILE__);
}
include("./comments.html");
这题应该是HITCON2017的一道题的改版,通过阅读代码,思路如下:
上传一个phar包改名为avatar.gif,然后上传到vps,upload上去,然后check的时候触发反序列化,然后包含进来,执行命令
参考:https://xz.aliyun.com/t/3190
这里触发反序列化是用到了getimagesize($file_path)这个函数。
然后利用类似
参考:https://blog.zsxsoft.com/post/38中提到的来绕过正则
最终得到flag
L playground2
这道题是赛后半个小时后做出来的,都怪在线逆pyc辣鸡2333
打开得到题目源码
import re
import os
http_schema = re.compile(r"https?")
url_parser = re.compile(r"(\w+)://([\w\-@\.:]+)/?([\w/_\-@&\?\.=%()]+)?(#[\w\-@&_\?()/%]+)?")
base_dir = os.path.dirname(os.path.abspath(__file__))
sandbox_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sandbox")
def parse_file(path):
filename = os.path.join(sandbox_dir, path)
if "./" in filename or ".." in filename:
return "invalid content in url"
if not filename.startswith(base_dir):
return "url have to start with %s" % base_dir
if filename.endswith("py") or "flag" in filename:
return "invalid content in filename"
if os.path.isdir(filename):
file_list = os.listdir(filename)
return ", ".join(file_list)
elif os.path.isfile(filename):
with open(filename, "rb") as f:
content = f.read()
return content
else:
return "can't find file"
def parse(url):
fragments = url_parser.findall(url)
if len(fragments) != 1 or len(fragments[0]) != 4:
return("invalid url")
schema = fragments[0][0]
host = fragments[0][1]
path = fragments[0][2]
if http_schema.match(schema):
return "It's a valid http url"
elif schema == "file":
if host != "sandbox":
return "wrong file path"
return parse_file(path)
else:
return "unknown schema"
@app.route('/sandbox')
def render_static():
url = request.args.get("url")
try:
if url is None or url == "":
content = "no url input"
else: