2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-07-18 08:26:50 +00:00

Further mitigate potential XML misbehavior

Since it turns out we already incurred lxml dependency, use lxml etree instead of xml and mitigate risky xml features beyond blocking the word '!entity'
This commit is contained in:
Jarrod Johnson
2026-07-01 08:28:25 -04:00
parent fbec09c073
commit 33c67db3c4
7 changed files with 39 additions and 14 deletions
@@ -49,17 +49,18 @@ class Unsupported(Exception):
pass
def fromstring(inputdata):
parser = etree.XMLParser(resolve_entities=False, no_network=True, huge_tree=False)
if b'!entity' in inputdata.lower():
raise Exception('Unsupported XML')
try:
return etree.fromstring(inputdata)
return etree.fromstring(inputdata, parser=parser)
except etree.XMLSyntaxError:
inputdata = bytearray(inputdata.decode('utf8', errors='backslashreplace').encode())
for i in range(len(inputdata)):
if inputdata[i] < 0x20 and inputdata[i] not in (9, 0xa, 0xd):
inputdata[i] = 63
inputdata = bytes(inputdata)
return etree.fromstring(inputdata)
return etree.fromstring(inputdata, parser=parser)
@@ -16,7 +16,7 @@ import asyncio
import fnmatch
import struct
import weakref
from xml.etree.ElementTree import fromstring as rfromstring
from lxml import etree
import zipfile
@@ -50,7 +50,8 @@ psutypes = {
def fromstring(inputdata):
if b'!entity' in inputdata.lower():
raise Exception('!ENTITY not supported in this interface')
return rfromstring(inputdata)
parser = etree.XMLParser(resolve_entities=False, no_network=True, huge_tree=False)
return etree.fromstring(inputdata, parser=parser)
def stringtoboolean(originput, name):
@@ -26,7 +26,7 @@ except ImportError:
import confluent.netutil as netutil
import confluent.util as util
from xml.etree.ElementTree import fromstring as rfromstring
import lxml.etree as etree
def fromstring(inputdata):
if isinstance(inputdata, bytes):
@@ -37,7 +37,8 @@ def fromstring(inputdata):
raise Exception('!ENTITY not supported in this interface')
# The measures above should filter out the risky facets of xml
# We don't need sophisticated feature support
return rfromstring(inputdata) # nosec
parser = etree.XMLParser(resolve_entities=False, no_network=True, huge_tree=False)
return etree.fromstring(inputdata, parser=parser)
def fixuuid(baduuid):
# SMM dumps it out in hex
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from xml.etree.ElementTree import fromstring as rfromstring
from lxml import etree
import confluent.util as util
import confluent.messages as msg
import confluent.exceptions as exc
@@ -90,7 +90,8 @@ def fromstring(inputdata):
raise Exception('!ENTITY not supported in this interface')
# The measures above should filter out the risky facets of xml
# We don't need sophisticated feature support
return rfromstring(inputdata) # nosec
parser = etree.XMLParser(resolve_entities=False, no_network=True, huge_tree=False)
return etree.fromstring(inputdata, parser=parser) # nosec
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-').replace(
@@ -18,9 +18,10 @@ import asyncio
import confluent.util as util
import confluent.messages as msg
import confluent.exceptions as exc
from xml.etree.ElementTree import fromstring as rfromstring
import lxml.etree as etree
def fromstring(inputdata):
parser = etree.XMLParser(resolve_entities=False, no_network=True, huge_tree=False)
if isinstance(inputdata, bytes):
cmpstr = b'!entity'
else:
@@ -29,7 +30,7 @@ def fromstring(inputdata):
raise Exception('!ENTITY not supported in this interface')
# The measures above should filter out the risky facets of xml
# We don't need sophisticated feature support
return rfromstring(inputdata) # nosec
return etree.fromstring(inputdata, parser=parser) # nosec
import http.client as httplib
@@ -1,5 +1,6 @@
import asyncio
import confluent.exceptions as exc
import confluent.vinzmanager as vinzmanager
import confluent.util as util
import confluent.messages as msg
@@ -193,6 +194,8 @@ class PmxApiClient:
}
loginbody = urlparse.urlencode(loginform)
rsp = await self.wc.grab_json_response_with_status('/api2/json/access/ticket', loginbody, headers={'Content-Type': 'application/x-www-form-urlencoded'})
if rsp[1] == 401:
raise exc.TargetEndpointBadCredentials()
self.pac = rsp[0]['data']['ticket']
self.wc.cookies.update_cookies({'PVEAuthCookie': self.pac})
self.wc.set_header('CSRFPreventionToken', rsp[0]['data']['CSRFPreventionToken'])
@@ -374,9 +377,12 @@ def prep_proxmox_clients(nodes, configmanager):
cfg = cfginfo[node]
currpmx = cfg['hardwaremanagement.manager']['value']
if currpmx not in clientsbypmx:
user = cfg.get('secret.hardwaremanagementuser', {}).get('value', None)
passwd = cfg.get('secret.hardwaremanagementpassword', {}).get('value', None)
clientsbypmx[currpmx] = PmxApiClient(currpmx, user, passwd, configmanager)
user = cfg.get('secret.hardwaremanagementuser', {}).get('value', None)
passwd = cfg.get('secret.hardwaremanagementpassword', {}).get('value', None)
try:
clientsbypmx[currpmx] = PmxApiClient(currpmx, user, passwd, configmanager)
except exc.TargetEndpointBadCredentials as e:
clientsbypmx[currpmx] = e
clientsbynode[node] = clientsbypmx[currpmx]
return clientsbynode
@@ -384,6 +390,9 @@ async def retrieve(nodes, element, configmanager, inputdata):
clientsbynode = prep_proxmox_clients(nodes, configmanager)
for node in nodes:
currclient = clientsbynode[node]
if isinstance(currclient, exc.TargetEndpointBadCredentials):
yield msg.ConfluentNodeError(node, "Bad credentials")
continue
if element == ['power', 'state']:
yield msg.PowerState(node, await currclient.get_vm_power(node))
elif element == ['boot', 'nextdevice']:
@@ -405,6 +414,9 @@ async def update(nodes, element, configmanager, inputdata):
clientsbynode = prep_proxmox_clients(nodes, configmanager)
for node in nodes:
currclient = clientsbynode[node]
if isinstance(currclient, exc.TargetEndpointBadCredentials):
yield msg.ConfluentNodeError(node, "Bad credentials")
continue
if element == ['power', 'state']:
newstate, oldstate = await currclient.set_vm_power(node, inputdata.powerstate(node))
yield msg.PowerState(node, newstate, oldstate)
@@ -425,6 +437,9 @@ async def update(nodes, element, configmanager, inputdata):
async def create(nodes, element, configmanager, inputdata):
clientsbynode = prep_proxmox_clients(nodes, configmanager)
for node in nodes:
if isinstance(clientsbynode[node], exc.TargetEndpointBadCredentials):
yield msg.ConfluentNodeError(node, "Bad credentials")
continue
if element == ['console', 'ikvm']:
try:
currclient = clientsbynode[node]
+6 -1
View File
@@ -1,9 +1,14 @@
#!/usr/bin/python3
import pyghmi.util.webclient as webclient
from xml.etree.ElementTree import fromstring
import lxml.etree as etree
import os
import sys
def fromstring(inputdata):
parser = etree.XMLParser(resolve_entities=False, no_network=True, huge_tree=False)
# The measures above should filter out the risky facets of xml
return etree.fromstring(inputdata, parser=parser)
tmppassword = 'to3BdS91ABrd'
missingargs = False
if 'SMMUSER' not in os.environ: