Una mala calidad del código lleva a un comportamiento no predecible. Desde la perspectiva de un usuario, muchas veces también supone una usabilidad limitada. Pero para un atacante es una oportunidad para atacar al sistema de formas insospechadas.
Camera
una vez que se ha liberado.Camera
una vez que se ha liberado. Todas las referencias adicionales al objeto Camera
sin que se vuelva a adquirir el recurso generarán una excepción y pueden provocar que se bloquee la aplicación si no se detecta la excepción.startPreview()
en el objeto Camera
liberado anteriormente.
public class ReuseCameraActivity extends Activity {
private Camera cam;
...
private class CameraButtonListener implements OnClickListener {
public void onClick(View v) {
if (toggle) {
cam.stopPreview();
cam.release();
}
else {
cam.startPreview();
}
toggle = !toggle;
}
}
...
}
start()
en el recurso multimedia liberado anteriormente.
public class ReuseMediaPlayerActivity extends Activity {
private MediaPlayer mp;
...
private class PauseButtonListener implements OnClickListener {
public void onClick(View v) {
if (paused) {
mp.pause();
mp.release();
}
else {
mp.start();
}
paused = !paused;
}
}
...
}
flushUpdates()
para confirmar los cambios en el disco. El método cierra correctamente el controlador de base de datos tras escribir las actualizaciones en la base de datos. Sin embargo, cuando se llama de nuevo a flushUpdates()
, se hace referencia de nuevo al objeto de base de datos sin reinicializarlo.
public class ReuseDBActivity extends Activity {
private myDBHelper dbHelper;
private SQLiteDatabase db;
@Override
public void onCreate(Bundle state) {
...
db = dbHelper.getWritableDatabase();
...
}
...
private void flushUpdates() {
db.insert(cached_data); // flush cached data
dbHelper.close();
}
...
}
...
<script src="http://applicationserver.application.com/lib/jquery/jquery-1.4.2.js" type="text/javascript"></script>
...
String
puede suponer una pérdida de datos.String
, no se especifica lo que ocurrirá con los datos que quedan fuera del conjunto de caracteres correspondiente. Esto puede suponer una pérdida de datos o una disminución en el nivel de seguridad cuando se necesitan datos binarios para garantizar que se cumplen las medidas de seguridad apropiadas.
...
FileInputStream fis = new FileInputStream(myFile);
byte[] byteArr = byte[BUFSIZE];
...
int count = fis.read(byteArr);
...
String fileString = new String(byteArr);
String fileSHA256Hex = DigestUtils.sha256Hex(fileString);
// use fileSHA256Hex to validate file
...
BUFSIZE
, funcionará siempre y cuando la información de myFile
se encuentre codificada igual que el conjunto de caracteres predeterminado. Sin embargo, si utiliza una codificación diferente o es un archivo binario, se perderá información. A su vez, esto provocará que el hash de SHA resultante no sea de confianza y que sea más fácil causar conflictos, especialmente si hay algún dato fuera del conjunto de caracteres predeterminado que se represente con el mismo valor, como un signo de interrogación.notify()
.notify()
. notifyJob()
llama notify()
.
public synchronized notifyJob() {
flag = true;
notify();
}
...
public synchronized waitForSomething() {
while(!flag) {
try {
wait();
}
catch (InterruptedException e)
{
...
}
}
...
}
wait()
, pero es posible que notify()
notifique un subproceso diferente al previsto.run()
del subproceso en lugar de llamar a start()
.run()
del objeto Thread
indica un error. El programador tenía intención de iniciar un nuevo subproceso de control, pero llamó accidentalmente a run()
en lugar de a start()
, por lo que el método run()
se ejecutará en el subproceso de control del autor de la llamada.run()
en lugar de a start()
.
Thread thr = new Thread() {
public void run() {
...
}
};
thr.run();
stop()
del subproceso, lo que puede filtrar recursos.stop()
de un objeto Thread
es un error. El programador pretende detener la ejecución de un subproceso pero no es consciente de que esa no es la mejor forma de detener un subproceso. La función stop()
en Thread
produce una excepción ThreadDeath
en cualquier lugar del objeto Thread
, lo que puede dejar a los objetos en un estado de incoherencia y filtrar recursos. Hace mucho que esta API está en desuso debido a que no es segura por naturaleza.Thread.stop()
.
...
public static void main(String[] args){
...
Thread thr = new Thread() {
public void run() {
...
}
};
...
thr.start();
...
thr.stop();
...
}
clone()
, pero no la interfaz Cloneable
.Cloneable
, ya que implementa un método llamado clone()
. Sin embargo, la clase no implementa la interfaz Cloneable
y el método clone()
no se comportará correctamente. clone()
para esta clase dará lugar a una CloneNotSupportedException.
public class Kibitzer {
public Object clone() throws CloneNotSupportedException {
...
}
}
ICloneable
especifica un contrato débil para su método Clone
, por lo que debe evitarse.ICloneable
no garantiza una clonación profunda; es posible que las clases que la implementan no se comporten en la forma prevista cuando se clonen. Las clases que implementan ICloneable
y realizan solo clonaciones superficiales (se copia solo el objeto, lo que incluye las referencias a otros objetos) pueden provocar un comportamiento inesperado. Como la clonación profunda (se copia el objeto y todos los objetos de referencia) suele ser normalmente el comportamiento previsto de un método de clonación, el uso de la interfaz ICloneable
es propenso a errores, por lo que debe evitarse.clone()
de la clase llama a una función que puede anularse.clone()
llama a una función que se puede sobrescribir, puede que el clon se quede en un estado parcialmente inicializado o que se dañe.clone()
llama a un método que se puede sobrescribir.
...
class User implements Cloneable {
private String username;
private boolean valid;
public Object clone() throws CloneNotSupportedException {
final User clone = (User) super.clone();
clone.doSomething();
return clone;
}
public void doSomething(){
...
}
}
doSomething()
y su clase envolvente no son final
, la función se puede sobrescribir, lo que podría dejar al objeto clonado clone
en un estado parcialmente inicializado, lo que podría dar lugar a errores o que funcionase en torno a la lógica de forma inesperada.equals()
, puede producirse un comportamiento inesperado.equals()
del primitivo boxed en vez de a los operadores ==
y !=
. La especificación Java establece lo siguiente sobre las conversiones boxing: Boolean
o Byte
), solo se almacenará en la caché o se memorizará un rango de valores. En el caso de un subconjunto de valores, el uso de ==
o !=
devolverá el valor correcto. Para los demás valores fuera de dicho subconjunto, se devolverá el resultado de comparar las direcciones de los objetos.
...
Integer mask0 = 100;
Integer mask1 = 100;
...
if (file0.readWriteAllPerms){
mask0 = 777;
}
if (file1.readWriteAllPerms){
mask1 = 777;
}
...
if (mask0 == mask1){
//assume file0 and file1 have same permissions
...
}
...
Example 1
utiliza primitivos boxed Integer
para intentar comparar dos valores int
. Si mask0
y mask1
son iguales a 100
, entonces mask0 == mask1
devolverá true
. Sin embargo, cuando mask0
y mask1
son iguales a 777
, mask0 == maske1
devolverá false
, ya que dichos valores no están dentro del rango de valores almacenados en la caché para estos primitivos boxed.NaN
siempre es un error.NaN
, siempre se evalúa como false
, excepto en el caso del operador !=
, que siempre se evalúa como true
, puesto que NaN
no está ordenado.NaN
.
...
if (result == Double.NaN){
//something went wrong
throw new RuntimeException("Something went wrong, NaN found");
}
...
result
no es NaN
. Sin embargo, si se utiliza el operador ==
con NaN
siempre da como resultado un valor de false
, así que esta comprobación nunca produce la excepción.this
antes de que el objeto se inicialice por completo, lo que a su vez puede suponer vulnerabilidad.
...
class User {
private String username;
private boolean valid;
public User(String username, String password){
this.username = username;
this.valid = validateUser(username, password);
}
public boolean validateUser(String username, String password){
//validate user is real and can authenticate
...
}
public final boolean isValid(){
return valid;
}
}
validateUser
y la clase no son final
, significa que se pueden sobrescribir y, en consecuencia, al inicializar una variable en la subclase que sobrescribe dicha función, será posible eludir la funcionalidad validateUser
. Por ejemplo:
...
class Attacker extends User{
public Attacker(String username, String password){
super(username, password);
}
public boolean validateUser(String username, String password){
return true;
}
}
...
class MainClass{
public static void main(String[] args){
User hacker = new Attacker("Evil", "Hacker");
if (hacker.isValid()){
System.out.println("Attack successful!");
}else{
System.out.println("Attack failed");
}
}
}
Example 1
imprime "¡Ataque satisfactorio!" porque la clase Attacker
sobrescribe la función validateUser()
, llamada desde el constructor de la superclase User
, y Java comprobará primero la subclase en busca de funciones llamadas desde el constructor.inputReader
en función de su nombre de clase. Si un usuario malintencionado es capaz de suministrar una implementación de inputReader
que ejecute comandos maliciosos, el código no puede diferenciar entre las versiones benignas y maliciosas del objeto.
if (inputReader.GetType().FullName == "CompanyX.Transaction.Monetary")
{
processTransaction(inputReader);
}
inputReader
en función de su nombre de clase. Si un usuario malintencionado es capaz de suministrar una implementación de inputReader
que ejecute comandos maliciosos, el código no puede diferenciar entre las versiones benignas y maliciosas del objeto.
if (inputReader.getClass().getName().equals("com.example.TrustedClass")) {
input = inputReader.getInput();
...
}
inputReader
en función de su nombre de clase. Si un usuario malintencionado es capaz de suministrar una implementación de inputReader
que ejecute comandos maliciosos, el código no puede diferenciar entre las versiones benignas y maliciosas del objeto.
if (inputReader::class.qualifiedName == "com.example.TrustedClass") {
input = inputReader.getInput()
...
}
x = NULL
y x != NULL
siempre serán falsas.NULL
es indeterminado. No es igual a ningún valor, ni incluso a otro valor NULL
. Además, un valor null
nunca es igual a otro valor.Ejemplo 2: la siguiente instrucción siempre será falsa.
checkNull BOOLEAN := x = NULL;
checkNotNull BOOLEAN := x != NULL;
equals()
, no con ==
o !=
.==
o !=
para comparar la igualdad de dos cadenas; es decir, compara la igualdad de dos objetos, no de sus valores. Hay muchas probabilidades de que dos referencias nunca sean iguales.
if (args[0] == STRING_CONSTANT) {
logger.info("miracle");
}
==
y !=
solo se comportarán según lo previsto cuando se utilicen para comparar cadenas dentro de objetos iguales. La forma más común para que esto ocurra es que las cadenas sean internas. De este modo, las cadenas se añaden a un grupo de objetos que la clase String
mantiene. Una vez que la cadena se interna, todos los usos de dicha cadena emplearán el mismo objeto y los operadores de igualdad se comportarán según lo previsto. Todos los literales de cadena y las constantes con valor de cadena se internan automáticamente. Otras cadenas pueden internarse manualmente llamando String.intern()
, lo que devolverá una instancia canónica de la cadena actual, creando una si fuera necesario.