Es bueno cuando hay alguien más experimentado en el equipo que mostrará qué y cómo hacer, qué rastrillo y qué ángulo están esperando, y dónde descargar los mejores dibujos de bicicletas para 2007 en DVD. Esta historia trata sobre cómo se dio el deseo como válido, cuál fue el resultado y cómo se superó la crisis.
Esto sucedió en un momento en que, teniendo, me parecía, una experiencia mediocre en el desarrollo, estaba buscando un lugar donde se pueda evolucionar (o mutar) de un no junior a un junior seguro. En formas misteriosas del Señor, se encontró un lugar así, se adjuntó un proyecto al lugar y al programador de la "vieja escuela", que escribió más que las niñas sobre su carrera en sistemas. “Genial! El proyecto, y por lo tanto hay dinero para la solicitud de propuesta, el mentor está adjunto, ¡vivimos! Pensé, pero luego, como en la descripción de un horror típico, los héroes en la oscuridad oscura enfrentaron un horror terrible ...
Primero lo primero:
1. El tamaño importa
Comenzamos el desarrollo en un motor php que alguna vez fue propietario, utilizado para almacenar datos (aquí podría pensar MySQL \ PostgreSQL \ SQLite \ MongoDB \ Something-else-but-necesariamente-with-suffix DB-else- chicos, no entiendo, pero no lo adivinaron) api-gateway.
“Jaja, usando php, ¿le adjuntas otra puerta de enlace api y almacenas datos en ella? ¿No es más fácil trabajar con api directamente desde js-code? ¿O utilizar DBMS + PHP? - Pregúntale al lector experimentado. Y tendrá razón. Pero en ese momento, yo, que aún no había visto la especie, no lo creía, bueno, quién sabe, los chicos geniales probablemente lo hacen, y los programadores de la "vieja escuela" lo saben mejor.
Como me explicaron más a fondo:
- Gateway = seguridad, nadie entrará y saldrá así
- Gateway = almacenamiento de datos seguro, simplemente no puede acceder a él, + copias de seguridad
- Gateway = velocidad, funciona rápidamente y sin fallas, probado en el tiempo
- El punto de vista autorizado de los programadores de la "vieja escuela" es que su php está lleno de agujeros, cualquier aplicación web está pirateada de forma predeterminada, por lo que no hay nada para almacenar datos al lado
Una característica de la puerta de enlace api era que los datos json se transmitían en una solicitud get. Sí, sí, esos encantadores objetos json, fueron sometidos a codificación url y se colocaron en la cadena de consulta. Y todo estaría bien, cuando de repente un día ... la duración de la solicitud de recepción dejó de ser suficiente. ¡Estúpidamente, el json codificado en url, kanalya, no encajaba allí! El programador de la "vieja escuela", rascándose la cabeza, preguntó:
“¿Qué vamos a hacer? Nuestro json ha crecido, pero no nos hemos dado cuenta ... "
"Bueno, uh, ¿tal vez los publicaremos en la publicación?" Sugerí, por lo que parece ser más correcto.
"Ok, pase para publicar".
Era como el número uno.
2. Gestión de tiempo y respaldo
Para atornillar una nueva funcionalidad al proyecto, fue necesario implementar
solicitudes CRUD correspondientes en la puerta de enlace, que es exactamente lo que realmente hizo nuestro compañero de la "vieja escuela". El problema era que hacía esto una vez cada 3 días, dando "Hecho, cheque". Los controles a veces mostraron que no todo funcionó, por ejemplo, obtener la lista está bien, agregar un nuevo elemento no está bien. Tomó algún tiempo arreglar y refinar, después de lo cual fue posible liberar la funcionalidad en el acceso masivo. La propuesta de participar en la implementación de consultas en la puerta de enlace usted mismo, porque es al menos más rápida, fue rechazada, porque "es difícil allí, no lo entenderá". El resultado de este enfoque fue el cierre del trabajo "sobre mí mismo". Si, por ejemplo, fue necesario arreglar algo en masa en la base de datos, entonces, eligiendo entre 3 días de espera y la implementación de correcciones por medio de consultas, elegí la segunda opción. A los clientes no les gustaba especialmente esperar, el nuevo lanzamiento voló de manera estable. Una de esas introductorias, a saber, la colocación masiva de un letrero a los usuarios de algún letrero, me fue encomendada para implementar, hubo una hora para todo sobre todo, las autoridades estaban esperando un hermoso informe. Aquí nos espera atas número dos-s.
El hecho es que el formato de los datos json transmitidos en las solicitudes implicaba solo unos pocos campos obligatorios, todos los demás eran arbitrarios, no existía una estructura clara y final. Por ejemplo, para agregar un usuario, pasé un json del formulario:
POST /api/users { "email":"ivanov@mail.ru", "password":"myEmailIsVeryBig", "name_last":"", "name_first":"", "name_middle":"", "birth":"01.01.1961", // , - "living_at":"., .3 .4 .24", "phone_num":"+70000000000" }
La parte opcional que se transmitió en las solicitudes de agregar / actualizar se guardó y se proporcionó en su totalidad (a continuación, explicaré cómo se implementó). La conclusión es que, el tiempo no se detiene, sería necesario resolver el problema: actualizar a los usuarios, colocar sus etiquetas. ¿Pero no conduces toda la estructura cada vez? Debe verificar! Lo probé en mí mismo: transmití solo un campo en la solicitud de actualización, verifiqué, apareció el campo, el resto de los datos está en su lugar. El punto es pequeño: realiza un bucle y actualiza el resto.
El guión resopló suavemente, recibiendo y transmitiendo datos, y todo parecía ir bien ... cuando de repente, una llamada. "¡No vemos el nombre de los usuarios en el sistema!" - Informe desde ese extremo del cable. "Vamos! ¡Pues funcionó! - Un escalofrío desagradable me recorrió la espalda. La investigación adicional mostró que, de hecho, el nombre "" se indicaba en el nombre, aunque todos los demás datos estaban en su lugar. ¿Qué hacer en tal situación? Implementar copia de seguridad!
Programador "camarada" de la vieja escuela ", ¡sin problemas! Necesita respaldo! ¿Cuándo se hace lo último relevante? - pregunto
"Uhh ... ya veré ... No, no hay bakapa ".
La situación se salvó por el hecho de que un par de horas antes finalicé y probé el módulo con informes, tuve un csv-box con todos los datos necesarios, el pedido se restableció en otra hora.
Falta de documentación inteligible, descripciones de algoritmos de trabajo, verificaciones de validez de entrada y, lo más importante, copias de seguridad de bases de datos, atas número dos.
Desde entonces, las copias de seguridad comenzaron a eliminarse todos los días.
3. Golpe profundo
Tembloroso, pero el trabajo se movía, los problemas se resolvieron, algunos más rápido, otros más lentamente, cuando de repente ... los clientes se dieron cuenta de que el sistema no era entendido por los servidores de otra persona, y por esa actitud hacia PD y la organización de actividades de ZI en ISPD No acariciarán la cabeza. Es necesario transferir el servidor a usted mismo.
¿Por qué el sistema no se transfirió originalmente? El liderazgo tenía una pasión: la centralización. ¡La gerencia soñó con un sistema que haría todo! ¿Necesitas unir a un niño a la escuela? Entras al sistema, en una oficina especial, allí envías una solicitud. Debe, por ejemplo, pedir una pizza: ingresa al sistema, a otra oficina especial, solicita pizza. ¿Quizás quisiste comunicarte con hermosas damas / caballeros? A su servicio hay un tercer gabinete especial: también está presentando una solicitud allí, y así sucesivamente hasta el infinito.
Ventajas: un nombre de usuario y contraseña para todo, los datos se almacenan de forma segura en la puerta de enlace. Incluso hay copias de seguridad. Y, fíjate, ¡nadie nos quitará este sistema! E incluso si se lo quita, ¿qué sigue? De todos modos, no entenderán el sistema de protección contra los programadores de la "vieja escuela": todo es complicado allí.
VDS con el sistema fue descargado, atribuido a los clientes, lo implementaron, todos bailan y cantan, ¡belleza!
Y luego una ola de curiosidad y algunas sospechas me cubrieron.
Si nuestra aplicación web está llena de agujeros, ¿dónde están los datos? ¿Realmente te has quedado en otros servidores? ¿Y si deciden cerrar el sistema desde afuera, entonces todo colapsará?
Una simple comprobación mostró que los datos, así como los procesadores de la puerta de enlace, estaban en el mismo servidor. Y no, no fueron transferidos allí debido a la transferencia del servidor, siempre estuvieron allí.
Ahora tenía a esa disposición el desarrollo muy secreto de la "vieja escuela", que me puse a investigar. Por supuesto, la ingeniería inversa genial al estilo de los artículos de la revista Hacker, con ollydbg, compensaciones y otras cosas interesantes no funcionó, así que estoy compartiendo lo que tengo.
El desarrollo en sí se implementó en python, solo había archivos .pyc que podían descompilarse fácilmente en código legible. Francamente, tomó mucho tiempo, hasta 25 minutos, descubrir cómo funciona.
Entonces, el complejo sistema desarrollado por el programador de la "vieja escuela", que pocos pueden entender, consiste en:
- El script procesado por Apache, que realmente recibió la solicitud. ¿Qué hizo este guión? Abrió una conexión a un puerto localhost específico y pasó una solicitud allí con todos sus datos. Eso es todo. Los intereses van más allá.
- La parte del servidor que procesó las solicitudes del script. La lógica de sus acciones fue bastante interesante. En primer lugar, no había manipulación de datos en el código ni consultas en la base de datos; en su lugar, se llamaron a las funciones de la base de datos en PL \ SQL. Toda lógica, controles, etc., todo se estableció en la función de base de datos. El 50% de la secuencia de comandos era un diccionario que contenía el nombre de la solicitud, la función asociada a ella y los nombres de los parámetros de la función que deberían corresponder a los datos pasados en la línea de obtención de solicitud. Los datos JSON, si es necesario, se pasaron como un parámetro separado. Una característica de la organización de la parte del servidor fue la conexión de respaldo durante la autenticación del usuario. Si se encontró el nombre de usuario y la contraseña en la base de datos, se generó la ID de sesión y la instancia de conexión abierta se dobló en el diccionario (y se eliminó por el tiempo de espera de 10 minutos para que no se eliminara; había un método especial para extender la vida útil de la sesión), la clave era la ID de sesión, que está directamente en la base de datos no almacenado ¿Cómo se asocia exactamente la ID de sesión con los datos del usuario? Después de todo, ¿hay una solicitud de datos en la que no se transmite la ID de usuario? Funciona, lo que significa que algo está mal aquí.
Se dio un desarrollo muy difícil a la conciencia con dificultad y no tuvo prisa por revelar los secretos perdidos hace mucho tiempo de los maestros del pasado.
Increíble (Ir a> Definición, gracias a PhpStorm por comprender PL \ SQL), incomprensible para la mente del laico que sufre Verdadero conocimiento de la civilización perdida de los programadores de la vieja escuela. En general, cuando se conectaba, se generaba una tabla temporal en la función de verificación de datos de autenticación, en la que se almacenaba la identificación del usuario.
Esto fue solo el comienzo, se encontró una lista indicativa de vulnerabilidades graves:
- DDoS utilizando autenticación masiva (las conexiones estaban reservadas y, por lo tanto, descansaban en el límite de conexión DBMS, lo que, dada la posibilidad de extender el tiempo de vida de la sesión, hizo posible llenar completamente la memoria con conexiones, y el trabajo de los nuevos usuarios en el sistema sería imposible);
- falta de protección contra la fuerza bruta (el número de intentos fallidos de inicio de sesión no se detecta, no se almacena, no se verifica;
- falta de control sobre las acciones con entidades (por ejemplo, la lista de documentos solicitados por el usuario se emitió teniendo en cuenta la organización a la que está adjunto el usuario, y si conoce el ID del documento, puede completar con éxito la solicitud para actualizar / eliminar el documento, y la lista de usuarios es buena incluso sin las contraseñas, que, por cierto, se almacenaron en la base de datos en claro, sin hashing, podrían ser recibidas por cualquier persona).
Y otro problema grave no es un esquema formal de almacenamiento de datos. Como prometí anteriormente, estoy hablando de almacenar "cualquier campo" de JSON. No, no se almacenaron como una fila en la tabla. Se dividieron en pares clave-valor y se almacenaron en una tabla separada. Por ejemplo, para los usuarios había 2 tablas, usuarios y usuarios_datos (clave de cadena, valor de cadena), donde los datos se almacenaron realmente. El resultado de este enfoque fue un aumento en el tiempo con muestras complejas de la base de datos.
En realidad, esto fue suficiente para tomar e implementar la decisión de transferir el sistema a una nueva API, comprensible, documentada y respaldada.
Moraleja
Quizás este sistema es "Legacy", y el programador de la "vieja escuela" que lo creó es la esencia de Legacy.
Sin embargo, las conclusiones son las siguientes:
- Si le dicen "allí es difícil, no lo entenderá", significa que hay un atas completo
- Si son aplastados por la autoridad, entonces algo es impuro
- Confíe, pero verifique: la seguridad no es un estado, la seguridad es un proceso continuo, por lo tanto, es mejor verificar las cualidades declaradas de la realidad que descubrir más tarde que todos los usuarios de repente se convirtieron en "Ivanov Ivanov Ivanitch", pero no hay bacaps.