XML Injection
XML インジェクション
説明
XMLインジェクションは、XML設定ファイルやストリームに組み込む前に、ユーザー入力の適切な検証とサニタイズが不足していることから生じる脆弱性です。この見落としにより、攻撃者は悪意のあるXMLコンテンツをインジェクションしてアプリケーションを悪用することができます。XML構造を操作したり、悪意のあるエンティティを導入したりすることで、攻撃者はアプリケーションのロジックを妨害したり、データを改ざんしたり、機密情報を抽出したりする可能性があります。
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()
}
}
推奨事項
XMLインジェクションの脆弱性を緩和するには、以下を検討してください。
- 適切な入力検証およびサニタイズ技術を実装する。
- 期待される形式に対してユーザー入力を検証し、XMLタグやエンティティとして解釈される可能性のある文字を削除またはエスケープしてサニタイズする。
- データバインディングを安全に処理する安全なXML解析ライブラリやAPIの使用を検討する。
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()
}
}
リンク
標準
- 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