XML Injection
Inyección XML
Descripción
La inyección XML es una vulnerabilidad que surge de la falta de validación y saneamiento adecuados de la entrada del usuario antes de incorporarla a los archivos de configuración o flujos XML. Este descuido permite a un atacante explotar la aplicación inyectando contenido XML malicioso. Al manipular la estructura XML o introducir entidades maliciosas, un atacante puede potencialmente interrumpir la lógica de la aplicación, alterar los datos o extraer información confidencial.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'XML Injection Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> saveConfigFile() async {
final directory = await getApplicationDocumentsDirectory();
final configFile = File('${directory.path}/config.xml');
// Create XML data
final xmlData = '''
<root>
<username>${_usernameController.text}</username>
<password>${_passwordController.text}</password>
</root>
''';
// Save the XML to the configuration file
await configFile.writeAsString(xmlData);
}
void login() {
final username = _usernameController.text;
final password = _passwordController.text;
// Perform login logic
print('Performing login with username: $username and password: $password');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('XML Injection Demo'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Enter username:'),
TextField(
controller: _usernameController,
),
SizedBox(height: 16.0),
Text('Enter password:'),
TextField(
controller: _passwordController,
obscureText: true,
),
SizedBox(height: 16.0),
ElevatedButton(
onPressed: () async {
await saveConfigFile();
login();
},
child: Text('Login'),
),
],
),
),
);
}
}
import SwiftUI
struct ContentView: View {
@State private var username = ""
@State private var password = ""
var body: some View {
VStack {
Text("Enter username:")
TextField("Username", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
Text("Enter password:")
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
saveConfigFile()
login()
}) {
Text("Login")
}
}
.padding()
}
func saveConfigFile() {
// Create XML data
let xmlData = '''
<root>
<username>\(username)</username>
<password>\(password)</password>
</root>
'''
// Save the XML to the configuration file
let configFilePath = "config.xml"
do {
try xmlData.write(toFile: configFilePath, atomically: true, encoding: .utf8)
} catch {
print("Failed to write XML to file: \(error)")
}
}
func login() {
// Perform login logic
print("Performing login with username: \(username) and password: \(password)")
}
}
@main
struct XMLInjectionApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
class MainActivity : AppCompatActivity() {
private lateinit var usernameEditText: EditText
private lateinit var passwordEditText: EditText
private lateinit var loginButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
usernameEditText = findViewById(R.id.usernameEditText)
passwordEditText = findViewById(R.id.passwordEditText)
loginButton = findViewById(R.id.loginButton)
loginButton.setOnClickListener {
val username = usernameEditText.text.toString()
val password = passwordEditText.text.toString()
saveConfigFile(username, password)
login(username, password)
}
}
private fun saveConfigFile(username: String, password: String) {
val configFilePath = "config.xml"
try {
val configFile = File(configFilePath)
val xmlData = configFile.readText()
val docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = docBuilder.parse(xmlData.byteInputStream())
doc.documentElement.normalize()
val root = doc.documentElement
root.getElementsByTagName("username").item(0).textContent = username
root.getElementsByTagName("password").item(0).textContent = password
val transformer = TransformerFactory.newInstance().newTransformer()
val result = StreamResult(configFile)
val source = DOMSource(doc)
transformer.transform(source, result)
} catch (e: Exception) {
Toast.makeText(this, "Failed to save configuration file", Toast.LENGTH_SHORT).show()
}
}
private fun login(username: String, password: String) {
// Perform login logic
Toast.makeText(this, "Performing login with username: $username and password: $password", Toast.LENGTH_SHORT).show()
}
}
Recomendación
Para mitigar las vulnerabilidades de inyección XML, considere lo siguiente:
- Implemente técnicas adecuadas de validación y saneamiento de entradas.
- Valide la entrada del usuario frente a los formatos esperados y sanéela eliminando o escapando los caracteres que podrían interpretarse como etiquetas o entidades XML.
- Considere utilizar bibliotecas de análisis XML seguras o API que manejen el enlace de datos de forma segura.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:xml/xml.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'XML Injection Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> saveConfigFile() async {
final directory = await getApplicationDocumentsDirectory();
final configFile = File('${directory.path}/config.xml');
// Create XML data
final builder = XmlBuilder();
builder.processing('xml', 'version="1.0"');
builder.element('root', nest: () {
builder.element('username', nest: _usernameController.text);
builder.element('password', nest: _passwordController.text);
});
final xmlData = builder.build().toXmlString(pretty: true);
// Save the XML to the configuration file
await configFile.writeAsString(xmlData);
}
void login() {
final username = _usernameController.text;
final password = _passwordController.text;
// Perform login logic
print('Performing login with username: $username and password: $password');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('XML Injection Demo'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Enter username:'),
TextField(
controller: _usernameController,
),
SizedBox(height: 16.0),
Text('Enter password:'),
TextField(
controller: _passwordController,
obscureText: true,
),
SizedBox(height: 16.0),
ElevatedButton(
onPressed: () async {
await saveConfigFile();
login();
},
child: Text('Login'),
),
],
),
),
);
}
}
import Foundation
func main() {
let configFilePath = "config.xml"
// Load configuration file
guard let configFileURL = Bundle.main.url(forResource: "config", withExtension: "xml") else {
print("Failed to load configuration file")
return
}
guard let xmlData = try? Data(contentsOf: configFileURL) else {
print("Failed to read configuration file")
return
}
// Parse XML
let xmlDoc: XMLDocument
do {
xmlDoc = try XMLDocument(data: xmlData, options: .documentTidyXML)
} catch {
print("Failed to parse XML: \(error)")
return
}
guard let root = xmlDoc.rootElement() else {
print("Invalid XML structure")
return
}
// Read configuration values from user input
guard let username = getUserInput(prompt: "Enter username:"), let password = getUserInput(prompt: "Enter password:") else {
print("Invalid input")
return
}
// Update configuration values in XML
if let usernameElement = root.elements(forName: "username").first {
usernameElement.stringValue = username
}
if let passwordElement = root.elements(forName: "password").first {
passwordElement.stringValue = password
}
// Save the modified XML back to the configuration file
guard let modifiedXmlData = xmlDoc.xmlData(options: .nodePrettyPrint) else {
print("Failed to generate modified XML")
return
}
do {
try modifiedXmlData.write(to: configFileURL)
} catch {
print("Failed to write modified XML to file: \(error)")
return
}
// Use the configuration values
login(username: username, password: password)
}
func getUserInput(prompt: String) -> String? {
print(prompt, terminator: " ")
return readLine()
}
func login(username: String, password: String) {
// Perform login logic
print("Performing login with username: \(username) and password: \(password)")
}
// Start the program
main()
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.w3c.dom.Document
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
class MainActivity : AppCompatActivity() {
private lateinit var usernameEditText: EditText
private lateinit var passwordEditText: EditText
private lateinit var loginButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
usernameEditText = findViewById(R.id.usernameEditText)
passwordEditText = findViewById(R.id.passwordEditText)
loginButton = findViewById(R.id.loginButton)
loginButton.setOnClickListener {
val username = usernameEditText.text.toString()
val password = passwordEditText.text.toString()
saveConfigFile(username, password)
login(username, password)
}
}
private fun saveConfigFile(username: String, password: String) {
val configFilePath = "config.xml"
try {
val configFile = File(configFilePath)
val xmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
val rootElement = xmlDoc.createElement("root")
xmlDoc.appendChild(rootElement)
val usernameElement = xmlDoc.createElement("username")
usernameElement.appendChild(xmlDoc.createTextNode(username))
rootElement.appendChild(usernameElement)
val passwordElement = xmlDoc.createElement("password")
passwordElement.appendChild(xmlDoc.createTextNode(password))
rootElement.appendChild(passwordElement)
val transformer = TransformerFactory.newInstance().newTransformer()
val result = StreamResult(configFile)
val source = DOMSource(xmlDoc)
transformer.transform(source, result)
} catch (e: Exception) {
Toast.makeText(this, "Failed to save configuration file", Toast.LENGTH_SHORT).show()
}
}
private fun login(username: String, password: String) {
// Perform login logic
Toast.makeText(this, "Performing login with username: $username and password: $password", Toast.LENGTH_SHORT).show()
}
}
Enlaces
Estándares
- OWASP_MASVS_L1:
- MSTG_PLATFORM_2
- OWASP_MASVS_L2:
- MSTG_PLATFORM_2
- PCI_STANDARDS:
- REQ_2_2
- REQ_6_2
- REQ_6_3
- REQ_11_3
- OWASP_MASVS_v2_1:
- MASVS_CODE_4
- SOC2_CONTROLS:
- CC_2_1
- CC_3_4
- CC_4_1
- CC_7_1
- CC_7_2
- CC_7_4
- CC_7_5