First I tried to send normal data in the form and I got the following error: This is what I got about you from the database: no such column: password.
It seems weird and it sounds like a SQLi.
Trying to inject the POST parameters value doesn't work so I tried to inject the key instead, as suggested by the the error message.
So I replaced password with ' (a simple quote).
The server answered another error message: 'NoneType' object is not iterable.
This seems like a python error message, so the web server backend should be using python.
Then I tried a UNION-based injection: '%20UNION%20SELECT%201--%20- (' UNION SELECT 1-- -)
I got SELECTs to the left and right of UNION do not have the same number of result columns.
Now we are pretty sure we have a SQL injection.
We can continue to add 1, to discover the number of columns, because I'm curious
Finaly I went up to 7 columns to stop getting an error: '%20UNION%20SELECT%201,1,1,1,1,1,1--%20- (' UNION SELECT 1,1,1,1,1,1,1-- -)
Now we have this cute JSON output leaking all the columns:
Finding the number of columns was not required, this works too: '%20UNION%20SELECT%20*%20from users--%20- (' UNION SELECT * from users-- -)
Note: finding the table users was just some easy guessing.
Hey! We leaked an user account!
We can iterate with '%20UNION%20SELECT%20*%20from users limit 1 offset 1--%20- (' UNION SELECT * from users limit 1 offset 1-- -)
Now we have two choices: first we can continue to iterate to dump the whole database until we find the username admin, like I did with this python script:
Or instead of dumping the whole database that is much more work and cost much more time, we could have just used '%20UNION%20SELECT%20*%20from%20users%20where%20username%3d"admin"--%20- (' UNION SELECT * from users where username="admin"-- -) to find the admin user directly.
We are in a python jail a.k.a. PyJail (python sandbox).
We know we must use get_flag() and wrap our input in double quotes.
Let's try to find more info:
So we see there is a variable called this_is_the_super_secret_string.
Then there is 48, 57, 65, 90, 97, 122, 44, 95, which converted from decimal to ASCII gives 09AZaz,_.
Then we have is not a valid character so we can deduce that we have a regex [0-9a-zA-Z_,] filtering the input.
Trying get_flag(get_flag.func_code.co_consts[1]) will give t is not a valid character, proving that there is such a filtering.
Fuzzing a little will throw this:
So we know our input is evaluated after being filtered.
Our goal is to get something like get_flag("this_is_the_super_secret_string") but not matching [0-9a-zA-Z_,].
Quick reminder, in python eval is used to evaluate an expression and returns its value whereas exec is a statement that compiles and executes a set of statements. In short this means you can execute statements when you are using exec but not when using eval.
[...]
Most of python are just references, and this we can see here again. These protections only remove references. The original modules like os, and the builtins are not altered in any way. Our task is quiet clear, we need to find a reference to something useful and use it to find the flag on the file system. But first we need to find a way of executing code with this little characters allowed.
where here, we can't use _ and ,. So our solution will be a little different.
Use sys.version we can know the version of python running:
As it is some python 2.7 we can use tuples (), lists [], dictionaries {:}, sets {}, strings ' ', % for formatting.
We can also use <, ~, `.
< and ~ are simple operators, we can do less-than comparison and binary-negation.
In python2 ` allows us to produce strings out of objects because `x` is equivalent to repr(x).
< can be used both for comparing to integers and in the form of << binary-shifting them to the left.
The first thing to notice is that []<[] is False, which is pretty logical. What is less explainable but serves us well is the fact that {}<[] evaluates to True.
True and False, when used in arithmetic operations, behave like 1 and 0. This will be the building block of our decoder but we still need to find a way to actually produce arbitrary strings.
Let's start with a generic solution, we will improve on it later. Getting the numeric ASCII values of our characters seems doable with True, False, ~ and <<. But we need something like str() or "%c". This is where the invisible characters come in handy! "\xcb" for example, it's not even ascii as it is larger than 127, but it is valid in a python string, and we can send it to the server.
If we take its representation using `'_\xcb_'` (In practice we will send a byte with the value 0xcb not '\xcb'), we have a string containing a c. We also need a '%', and we need those two, and those two only.
We want this: `'%\xcb'`[1::3] , using True and False to build the numbers we get:
There you go! Now provided we can have any number build using the same trick as for the indexes we just have to use the above and %(number) to get any character we want.
So we want to get something like "%c" % (70) to get a F.
If you have ever studied any logic you might have encountered the claim that everything could be done with NAND gates. NOT-AND. This is remarkably close to how we shall proceed, except for the fact we shall use multiply-by-two instead of AND. We won't use True.
Everything can be done using only False (0), ~ (not), << (x2), let me show you with an example. We shall go from 42 to 0 using ~ and /2, then we can revert that process using ~ and *2.
Basically we divided by two when we could, else we inverted all the bits. The nice property of this is that when inverting we are guaranteed to be able to divide by two afterward. So that finally we shall hit 1, 0 or -1.
But wait. Didn't I say we would not use True, 1? Yes I did, but I lied. We will use it because True is obviously shorter than ~(~False*2), especially considering the fact we will use True anyway to do x2, which in our case is of course <<({}<[]).
Anyway, the moment we hit 1, 0 or -1 we can just use True, False or ~False.
So now we can reverse this and we have:
Using what we are allowed to:
So by using this function, we can transform any number into a kind of logical brainfuck:
The first solution to a fast clone and saving developer’s and system’s time and disk space is to copy only recent revisions. Git’s shallow clone option allows you to pull down only the latest n commits of the repo’s history.