No es urgente responder. No es para producción. Tomense su tiempo para responder esta pregunta, porque es larga. Quiero una respuesta que no esté basada en lo que dicen los demas. Y quiero aprender de alguien que sepa de seguridad informática.
Resumiendo, el código es el siguiente:
<?php
require('./config.php');
require('./login.php');
function enviarErrorServidor() {
http_response_code(500);
die('Internal Server Error');
}
function enviarErrorPeticionHTTP() {
http_response_code(400);
die('Bad Request');
}
function enviarErrorRecursoNoEncontrado() {
http_response_code(404);
die('Not found');
}
function verificarNombre($nombre) {
if (empty($nombre) || !preg_match('/^\w+$/u', (string) $nombre)) {
enviarErrorPeticionHTTP();
}
}
function verificarClavePrimaria($numero) {
if (empty($numero) || !ctype_digit((string) $numero)) {
enviarErrorPeticionHTTP();;
}
}
function enviarDatosComoJSON($datos) {
header('Content-Type: application/json');
echo json_encode($datos);
exit();
}
function esCampoRegistro($nombreCampo) {
return !preg_match('/^_/', (string) $nombreCampo);
}
function crearRegistroTabla($nuevoRegistro, $nombreTabla) {
global $conexion;
verificarNombre($nombreTabla);
$nombresCampos = array();
$valoresCampos = array();
foreach ($nuevoRegistro as $nombreCampo => $valorCampo) {
verificarNombre($nombreCampo);
if (esCampoRegistro($nombreCampo)) {
if ($nombreCampo == 'id') {
enviarErrorPeticionHTTP();
}
$nombresCampos[] = $nombreCampo;
$valoresCampos[] = "'".$conexion->real_escape_string((string) $valorCampo)."'";
}
}
$nombresCampos = '('.implode(', ', $nombresCampos).')';
$valoresCampos = '('.implode(', ', $valoresCampos).')';
$operacionSQL = "INSERT INTO $nombreTabla $nombresCampos VALUES $valoresCampos";
if ($conexion->query($operacionSQL) === TRUE && $conexion->affected_rows === 1) {
enviarDatosComoJSON($conexion->insert_id);
} else {
enviarErrorServidor();
}
}
function modificarRegistroTabla($registroModificado, $nombreTabla) {
global $conexion;
verificarNombre($nombreTabla);
$camposRegistro = array();
$clavePrimaria = false;
foreach ($registroModificado as $nombreCampo => $valorCampo) {
verificarNombre($nombreCampo);
if ($nombreCampo == 'id') {
$clavePrimaria = $registroModificado['id'];
} else {
if (esCampoRegistro($nombreCampo)) {
$camposRegistro[] = "$nombreCampo = '".$conexion->real_escape_string((string) $valorCampo)."'";
}
}
}
verificarClavePrimaria($clavePrimaria);
$camposRegistro = implode(', ', $camposRegistro);
$operacionSQL = "UPDATE $nombreTabla SET $camposRegistro WHERE id = $clavePrimaria";
if ($conexion->query($operacionSQL) === TRUE) {
if ($conexion->affected_rows === 1) {
exit();
} else {
enviarErrorRecursoNoEncontrado();;
}
} else {
enviarErrorServidor();
}
}
function eliminarRegistroTabla($clavePrimaria, $nombreTabla) {
global $conexion;
verificarNombre($nombreTabla);
verificarClavePrimaria($clavePrimaria);
$operacionSQL = "DELETE FROM $nombreTabla WHERE id = $clavePrimaria";
if ($conexion->query($operacionSQL) === TRUE) {
if ($conexion->affected_rows === 1) {
exit();
} else {
enviarErrorRecursoNoEncontrado();;
}
} else {
enviarErrorServidor();
}
}
function obtenerRegistroTabla($clavePrimaria, $nombreTabla) {
global $conexion;
verificarNombre($nombreTabla);
verificarClavePrimaria($clavePrimaria);
$resultado = $conexion->query("SELECT * FROM $nombreTabla WHERE id = $clavePrimaria");
if ($resultado) {
if ($resultado->num_rows === 1) {
enviarDatosComoJSON($resultado->fetch_assoc());
} else {
enviarErrorRecursoNoEncontrado();;
}
} else {
enviarErrorServidor();
}
}
En el código, la variable $conexion es un objeto de MySQLi declarado anteriormente.
En varias parte del código, hago "type casting" o conversión al tipo "string" o cadena de texto. Eso es para evitar cualquier error que derive en alguna falla de seguridad. Además, MySQL, siempre que encuentre una cadena de texto en los valores de los campos de un registro, la convierte siempre al tipo de dato que corresponde. Lo descubrí hace poco, revisando codigo fuente ajeno.
Usé el método real_escape_string porque hace que el código sea mucho más facil de implementar. Pero tengo una duda existencial: ¿Es realmente segura esta función utilizada de esa manera, con respecto a inyecciones SQL?
Con respecto a las funciones que usan expresiones regulares, utilize el siguiente sitio web para comprobarlas: regex101.com. ¿Hay alguna falla de seguridad en esas expresiones regulares, con respecto a inyecciones SQL?
El archivo login.php, no es importante para el ejemplo, pero si, para la seguridad del sistema. Si no lo incluyo, el usuario no necesitaria iniciar sesion para usar el sistema.
Además, no uso esas funciones solas, sino que tambien realizo otras validaciones y sanitizaciones, previamente para evitar vulnerabilidades XSS.
En el archivo config.php esta la secuencia de conexión a la base de datos MySQL y su código fuente es el siguiente:
<?php
define('MODO_DESARROLLADOR', true);
$conexion = new mysqli('127.0.0.1', 'usuario', 'contraseña', 'base-de-datos');
if ($conexion->connect_errno > 0) {
if (MODO_DESARROLLADOR) {
die('Unable to connect to database [' . $conexion->connect_error . ']');
} else {
die('Internal Server Error');
}
}
$conexion->set_charset('utf8');
if (MODO_DESARROLLADOR) {
header('Access-Control-Allow-Origin: *');
} else {
error_reporting(0);
}
Si el código fuente no es seguro. ¿Cuales son sus fallas de seguridad?
Si debo usar sentencias preparadas (creo que es lo que recomiendan, pero me gustaria saber porqué).¿Es posible implementar el código anterior usando sentencias preparadas? Siendo de esa manera: ¿De que manera podría implementarlo?