Jak poprawnie uruchomić proces bez obawy o command injection?

0

Witam serdecznie,

Otrzymalam maly projekt na zaliczenie w ktorym potrzeba naprawic kilka luk w zabezpieczeniu aplikacji. Udalo mi sie uporac z SQLi i XSS, aczkolwiek bije glowa w sciane z OS command injection. Ponizej przedstawiam kod z dwoch plikow, kotre przekazuja "input" do cowsay.run. Wedlug OWASP,a potrzeba poprawnie przekazac argumenty i komende w "Processbuilderze" ale niestety nie bardzo mi to wychodzi. Jesli ktos bylby w stanie pomoc bylabym bardzo wdzieczna.

package com.scalesec.vulnado;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Cowsay {
  public static String run(String input) {
    ProcessBuilder processBuilder = new ProcessBuilder();
    String cmd = "/usr/games/cowsay '" + input + "'";
    System.out.println(cmd);
    processBuilder.command("bash", "-c", cmd);

    StringBuilder output = new StringBuilder();

    try {
      Process process = processBuilder.start();
      BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

      String line;
      while ((line = reader.readLine()) != null) {
        output.append(line + "\n");
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return output.toString();
  }
}
package com.scalesec.vulnado;

import org.springframework.web.bind.annotation.*;
import org.springframework.boot.autoconfigure.*;

import java.io.Serializable;

@RestController
@EnableAutoConfiguration
public class CowController {
    @RequestMapping(value = "/cowsay")
    String cowsay(@RequestParam(defaultValue = "I love Linux!") String input) {
        return Cowsay.run(input);
    }
}
0

Dodają jakąś walidacje inputu, jakiś regexp, sprawdź czy input ma zabronione znaki

4

Popatrz na Twój kawałek kodu

String cmd = "/usr/games/cowsay '" + input + "'";
System.out.println(cmd);
processBuilder.command("bash", "-c", cmd);

Masz w nim możliwość zespawnowania procesu używając argument-list, a jednak nie korzystasz z niego i sklejasz polecenie razem z input. To "sklejanie" to jest główna przyczyna podatności na command injection.

Nie możesz tego uruchomić tak?

processBuilder.command("/usr/games/cowsay", input);
0

Super dziekuje bardzo za podpowiedzi, zaraz powalcze :)

0

Ogólnie też miałem próby "zwalczenia" command injection i to była trochę walka z wiatrakami - najlepiej jak user miałby bardzo zawężoną listę akceptowalnych komend. Jeśli jest dowolność i może wykonywać dowolne komendy, to o wiele, wiele, wiele ciężej.

1
Pinek napisał(a):

Ogólnie też miałem próby "zwalczenia" command injection i to była trochę walka z wiatrakami - najlepiej jak user miałby bardzo zawężoną listę akceptowalnych komend. Jeśli jest dowolność i może wykonywać dowolne komendy, to o wiele, wiele, wiele ciężej.

Nie da się machnąć różdżką i zrobić że dane wejściowe "są bezpieczne". Musisz odpowiednio parsować dane wejściowe, i odpowiednio budować dane wyjściowe. Próby jakiegoś ręcznego obchodzenia tego regexpami albo blacklistami są gorszym rozwiązaniem pod tym względem, że możesz zapomnieć o jakimś case'ie.

Jeśli spawnujesz proces używając arguments list, i nigdzie nie sklejasz inputu razem z komendami to nic się złego nie stanie.

1
Pinek napisał(a):

Ogólnie też miałem próby "zwalczenia" command injection i to była trochę walka z wiatrakami - najlepiej jak user miałby bardzo zawężoną listę akceptowalnych komend. Jeśli jest dowolność i może wykonywać dowolne komendy, to o wiele, wiele, wiele ciężej.

Dobrze tylko nie na temat :P Bo OP ma ti dokładnie jedną komendę wbitą na sztywno i tylko input dla tej komendy pobiera :D
BTW Perl i TCL miały cos takiego jak tryb bezpieczny gdzie właśnie była white lista dozwolonych bezpiecznych komend. Na szybko widze że jest coś takiego jak Safe Mode dla całej JVM, ale nie można wymusić tego dla samych poleceń bashowych :(

0

Jeżeli masz ograniczoną liczbę funkcji które musisz obsłużyć to wrapujesz takie funkcje. Robisz buildera z możliwymi argumentami i każdy argument walidujesz pod kątem typu danych oraz formatu. Nie da ci to 100% zabezpieczenia ale też input usera nie będzie odpalany zywcem. Poza tym wtedy możesz robic personalizowane walidatory. Typu oczekujesz np wartości dla danego argumentu która jest poprawnym URI to nie przepuścisz tam żadnego innego stringu.

0

Ale co to ma robić? Bo to co napisałaś służy do uruchamiania dowolnej, przekazanej przez endpoint komendy w podanym katalogu, czyli dość ciężko będzie to zabezpieczyć przed uruchamianiem podanej przez endpoint komendy we wskazanym katalogu...
Zakładam, patrząc na dane wejściowe, że ma zrobić coś na zasadzie echo $arg. Czyli tak jak nie znam ProcessBuildera, to pewnie skończy się na czymś w stylu:

new ProcessBuilder("echo", arg).start();
4

Skoro możesz w miarę swobodnie modyfikować aplikację, to pomyśl o chroot. Może nie jest to idealne rozwiązanie, ale na początek daje radę. Trochę zabawniejsze i wredniejsze, to uruchamianie polecenia w kontenerze budowanym za pomocą testcontainers. Jednak tutaj trzeba mieć już dockera na pokładzie.

0

Z mojego doświadczenia wynika, że najlepszym rozwiązaniem jest zabronienie wszystkiego co znajduje się poza jakaś listą poleceń i parametrów możliwych do wykonania.

Używanie chroot wewnątrz aplikacji nic nie da, to jest zwalanie odpowiedzialności na system operacyjny, zachowanie chroot jest zależne od konfiguracji w systemie, czyli na zewnątrz naszej aplikacji.

A na potrzeby tego kodu i zaliczenia - sądzę że wystarczy ProcessBuilder z parametrem, czyli coś takiego jak napisał @Riddle

1 użytkowników online, w tym zalogowanych: 0, gości: 1