This weekend I played N1CTF with team r3kapig and finally we got the 2nd🥈 place. Thanks to all my teammates for their hard work, and also to the Nu1L team for holding the wonderful & awesome game.
Btw, writeup for all challenges we solved will be published here, check it out! I will write some of them in detail
This challenge is the easiest web challenge, but it is very interesting especially for those who are new in CTF. It gives the source code It is easy to know that it is an unserialize challenge, and we can ctrl the serialize string totally. It means that you can create Object with any attributes’ value
Then let’s find the pop chain. it is easy to know that the purpose is to call
flag->getflag() method by automatically calling
__destruct() magic method, and
$check attribute must be eq to a censored string
"key****************". So now what we need to do is getting this secret string. Fortunately there is a
ip class with a
__toString() magic method. If you treat an Object as a string, it will automatically call the object’s
__toString() method, and what
__toString returns will be used.
ip->__toString() will execute a mysql
insert query, and one of the values is
$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']), we can easily ctrl
X-Forwarded-For header so it hints that it is a SQL inject challenge. But we don’t know what
waf() method do but it must filter some sql keywords otherwise it would be a really really really easy challenge(in fact, even it has waf it is still not difficult). By fuzzing, we suppose that
waf() method may look like this:
according to the pattern, we cannot use Time-based Blind SQLi. But
insert query returns nothing useful, so now we must find a way to get the sub select query result. The first time I wanted to use mysql
load_file() to send a http request and use dnslog to receive the result but failed. Then I noticed that
ip->__toString() will return
mysqli_error($con), so maybe I can use Error-based SQLi. The exploit chain looks like:
flagclass’s member variable
$this-ipas a string, and now
ipobject instance, so it called
__toStringdo an sql query with error-based sqli payload(I’ll show you my payload later) in it because we controlled the XFF header. if the blind sqli expression in error-based sqli payload returns true,
__toStringwill return mysql error message, otherwise it returns “your ip looks ok!”
- since error-based sqli will return the sub select result, just decorate your payload to make sure it will return “n1ctf” in the hole error message when blind sqli returns true in sub select query(If you dont understand, dont worry, u will understand it after reading the payload). Then
$this->ipto “welcome to n1ctf2020”. If blind sqli returns false,
$this->ipwill be “noip”
__destruct()because it has nothing else to do, the object needs to be destroyed.
echo $this->ipbecause we still dont know the key now.
- So, we have an error-based sqli payload will a sub select query in it. And the sub select query is a blind sqli payload, what this sub query returns will be up to a boolean expression. This boolean expression will finally control what is echoed when
- According to the 2 different echo result, we can do Boolean-based SQL！
now I will show you my sqli payload:
'(select ip from n1ip where _updatexml(1,concat('~',(select if(ascii(substring((select database()),1,1))=100,'n1ctf','r3kapig')),'~'),3))'_
The italics are
updatexml() error-based sqli, the blue part are the sub query I mentioned above, the pink part are what you want to dump, and the underline part is the boolean expression I also mentioned above. I think it’s time to write exp
What need to be noticed is that you can’t dump key by
select key from n1key but
select `key` from n1key. because
key is a keyword of mysql and
`key` means a column named key. After dumping the key, we can get flag by unserialize again
I didnt solve this challenge because of several reasons, for example, my computer is out of power and it wouldn’t help us get champion. But this challege is quite interesting so that I decided to record our idea.
also provided Dockerfile:
You can give any filters you want, you can also give more than one filters divided by
/, then it will read file by using
php://filter stream and push the result to an array. After that
shuffle() function shuffles (randomizes the order of the elements in) an array. Finally write the array to a file and include the file. Obviously we need to RCE by using
require_once $source_file. It is interesting that flag is on the web root directory and it named itself as it content(
cat /tmp/flag > /var/www/html/`cat /tmp/flag` ). So the first code I wanna write into
$source_file to include is
<?=`ls`; Then what I need to do is get these chars(or substring of
<?=`ls`;), I write a simple fuzzer to get them.
To be honest, this fuzzer script has a lot to improve on. For example, it can fuzz more than one char. If it find some filters that can get
<?=` , the challenge will become very simple. It’s easy to improve but I didn’t because we have high performance servers and two of my teammates help me run the script(main reason is I’m lazy). Finally we fuzzed out all these 8 characters. since there are two
`, it just need to send about 7! = 5040 requests to let
shuffle() get those 8 chars in the right order. for example, these filters can get
BUT!!! when I prepare to get flag, I found that local environment is different from the challenge!
It may be challenge author’s fault. Maybe he changed the php binary file or some other reason. We can still solve this challenge by download /usr/bin/php firstly and locally fuzz again, but I gave up because it was unnecessary. Solving this challenge wouldn’t let us get the first prize, but we have found the right way to solve the challenge, and that is enough. Challenge author’s fuzzer script here This challenge also has an unintended solution, you can check out Super Guesser‘s Writeup
build on local by using
docker-compose up -d --build
This challenge is quite easy. After discussing with author, the intended solution is get a shell of zabbix server, but in fact it is totally unnecessary. What I did firstly is read zabbix document for more than 1h. First, login with Admin/zabbix
Then configure the host(zabbix agent) and make sure
zbx is green
by default it is red because of the wrong config
what we must need to configure the host is agent’s ip and port. I got them from docker
configure the host and make ZBX green(green means available)
ZABBIX is used to monitor the server. noticed that it is monitoring whether the /etc/passwd of the agent is modified by
vfs.file.cksum, which indicates that zabbix has the right to read files on agent.
and what is
vfs.file.cksum? it is an item
then I thought there must be an item to read file content, and it’s true
now let get flag by using this item. And I got the 2nd blood of this challenge.
Easy but interesting! In fact, at the first time I configured the item but I don’t know how to Get value. Then I changed to try to RCE. What I did is configure a trigger -> trigger an action -> action execute command. But I failed because by default the agent don’t support to execute command.