コンテンツにスキップ

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